package softlayer

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

type Client struct {
	User     string
	ApiKey   string
	Endpoint string
}

type HostSpec struct {
	Hostname                       string            `json:"hostname"`
	Domain                         string            `json:"domain"`
	Cpu                            int               `json:"startCpus"`
	Memory                         int               `json:"maxMemory"`
	Datacenter                     Datacenter        `json:"datacenter"`
	SshKeys                        []*SSHKey         `json:"sshKeys"`
	BlockDevices                   []BlockDevice     `json:"blockDevices"`
	InstallScript                  string            `json:"postInstallScriptUri"`
	PrivateNetOnly                 bool              `json:"privateNetworkOnlyFlag"`
	Os                             string            `json:"operatingSystemReferenceCode"`
	HourlyBilling                  bool              `json:"hourlyBillingFlag"`
	LocalDisk                      bool              `json:"localDiskFlag"`
	PrimaryNetworkComponent        *NetworkComponent `json:"primaryNetworkComponent,omitempty"`
	PrimaryBackendNetworkComponent *NetworkComponent `json:"primaryBackendNetworkComponent,omitempty"`
}

type NetworkComponent struct {
	NetworkVLAN *NetworkVLAN `json:"networkVlan"`
}

type NetworkVLAN struct {
	Id int `json:"id"`
}

type SSHKey struct {
	Key   string `json:"key,omitempty"`
	Id    int    `json:"id,omitempty"`
	Label string `json:"label,omitempty"`
}

type BlockDevice struct {
	Device    string    `json:"device"`
	DiskImage DiskImage `json:"diskImage"`
}

type DiskImage struct {
	Capacity int `json:"capacity"`
}

type Datacenter struct {
	Name string `json:"name"`
}

type sshKey struct {
	*Client
}

type VirtualGuest struct {
	*Client
}

func NewClient(user, key, endpoint string) *Client {
	return &Client{User: user, ApiKey: key, Endpoint: endpoint}
}

func (c *Client) isOkStatus(code int) bool {
	codes := map[int]bool{
		200: true,
		201: true,
		204: true,
		400: false,
		404: false,
		500: false,
		409: false,
		406: false,
	}

	return codes[code]
}

func (c *Client) newRequest(method, uri string, body interface{}) ([]byte, error) {
	var (
		client = &http.Client{}
		url    = fmt.Sprintf("%s/%s", c.Endpoint, uri)
		err    error
		req    *http.Request
	)

	if body != nil {
		bodyJSON, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		req, err = http.NewRequest(method, url, bytes.NewBuffer(bodyJSON))
	} else {
		req, err = http.NewRequest(method, url, nil)
	}

	if err != nil {
		return nil, fmt.Errorf("Error with request: %v - %q", url, err)
	}

	req.SetBasicAuth(c.User, c.ApiKey)
	req.Method = method

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if !c.isOkStatus(resp.StatusCode) {
		type apiErr struct {
			Err string `json:"error"`
		}
		var outErr apiErr
		json.Unmarshal(data, &outErr)
		return nil, fmt.Errorf("Error in response: %s", outErr.Err)
	}
	if err != nil {
		return nil, err
	}

	return data, nil
}

func (c *Client) SSHKey() *sshKey {
	return &sshKey{c}
}

func (c *sshKey) namespace() string {
	return "SoftLayer_Security_Ssh_Key"
}

func (c *sshKey) Create(label, key string) (*SSHKey, error) {
	var (
		method = "POST"
		uri    = c.namespace()
		body   = SSHKey{Key: key, Label: label}
	)

	data, err := c.newRequest(method, uri, map[string]interface{}{"parameters": []interface{}{body}})
	if err != nil {
		return nil, err
	}

	var k SSHKey
	if err := json.Unmarshal(data, &k); err != nil {
		return nil, err
	}

	return &k, nil
}

func (c *sshKey) Delete(id int) error {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%v", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *Client) VirtualGuest() *VirtualGuest {
	return &VirtualGuest{c}
}

func (c *VirtualGuest) namespace() string {
	return "SoftLayer_Virtual_Guest"
}

func (c *VirtualGuest) PowerState(id int) (string, error) {
	type state struct {
		KeyName string `json:"keyName"`
		Name    string `json:"name"`
	}
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/getPowerState.json", c.namespace(), id)
	)

	data, err := c.newRequest(method, uri, nil)
	if err != nil {
		return "", err
	}
	var s state
	if err := json.Unmarshal(data, &s); err != nil {
		return "", err
	}

	return s.Name, nil
}

func (c *VirtualGuest) ActiveTransaction(id int) (string, error) {
	type transactionStatus struct {
		AverageDuration string `json:"averageDuration"`
		FriendlyName    string `json:"friendlyName"`
		Name            string `json:"name"`
	}
	type transaction struct {
		CreateDate        string            `json:"createDate"`
		ElapsedSeconds    int               `json:"elapsedSeconds"`
		GuestID           int               `json:"guestId"`
		HardwareID        int               `json:"hardwareId"`
		ID                int               `json:"id"`
		ModifyDate        string            `json:"modifyDate"`
		StatusChangeDate  string            `json:"statusChangeDate"`
		TransactionStatus transactionStatus `json:"transactionStatus"`
	}
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/getActiveTransaction.json", c.namespace(), id)
	)

	data, err := c.newRequest(method, uri, nil)
	if err != nil {
		return "", err
	}
	var t transaction
	if err := json.Unmarshal(data, &t); err != nil {
		return "", err
	}

	return t.TransactionStatus.Name, nil
}

func (c *VirtualGuest) Create(spec *HostSpec) (int, error) {
	var (
		method = "POST"
		uri    = c.namespace() + ".json"
	)

	data, err := c.newRequest(method, uri, map[string]interface{}{"parameters": []interface{}{spec}})
	if err != nil {
		return -1, err
	}

	type createResp struct {
		ID int `json:"id"`
	}

	var r createResp
	if err := json.Unmarshal(data, &r); err != nil {
		return -1, err
	}

	return r.ID, nil
}

func (c *VirtualGuest) Cancel(id int) error {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%v", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *VirtualGuest) PowerOn(id int) error {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/powerOn.json", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *VirtualGuest) PowerOff(id int) error {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/powerOff.json", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *VirtualGuest) Pause(id int) error {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/pause.json", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *VirtualGuest) Resume(id int) error {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/resume.json", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *VirtualGuest) Reboot(id int) error {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/rebootSoft.json", c.namespace(), id)
	)

	_, err := c.newRequest(method, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

func (c *VirtualGuest) GetPublicIP(id int) (string, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/getPrimaryIpAddress.json", c.namespace(), id)
	)

	data, err := c.newRequest(method, uri, nil)
	if err != nil {
		return "", err
	}
	return strings.Replace(string(data), "\"", "", -1), nil
}

func (c *VirtualGuest) GetPrivateIP(id int) (string, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%v/getPrimaryBackendIpAddress.json", c.namespace(), id)
	)

	data, err := c.newRequest(method, uri, nil)
	if err != nil {
		return "", err
	}
	return strings.Replace(string(data), "\"", "", -1), nil
}
