package server

import (
	"fmt"
	"regexp"
	"time"

	"github.com/CenturyLinkCloud/clc-sdk/api"
	"github.com/CenturyLinkCloud/clc-sdk/status"
)

var (
	ErrInvalidServer = fmt.Errorf("server: server missing required field(s). (Name, CPU, MemoryGB, GroupID, SourceServerID, Type)")
)

func New(client api.HTTP) *Service {
	return &Service{
		client: client,
		config: client.Config(),
	}
}

type Service struct {
	client api.HTTP
	config *api.Config
}

func (s *Service) Get(name string) (*Response, error) {
	url := fmt.Sprintf("%s/servers/%s/%s", s.config.BaseURL, s.config.Alias, name)
	if regexp.MustCompile("^[0-9a-f]{32}$").MatchString(name) {
		url = fmt.Sprintf("%s?uuid=true", url)
	}
	resp := &Response{}
	err := s.client.Get(url, resp)
	return resp, err
}

func (s *Service) Create(server Server) (*status.QueuedResponse, error) {
	if !server.Valid() {
		return nil, ErrInvalidServer
	}

	resp := &status.QueuedResponse{}
	url := fmt.Sprintf("%s/servers/%s", s.config.BaseURL, s.config.Alias)
	err := s.client.Post(url, server, resp)
	return resp, err
}

func (s *Service) Update(name string, updates ...api.Update) (*status.Status, error) {
	resp := &status.Status{}
	url := fmt.Sprintf("%s/servers/%s/%s", s.config.BaseURL, s.config.Alias, name)
	err := s.client.Patch(url, updates, resp)
	return resp, err
}

func (s *Service) Edit(name string, updates ...api.Update) error {
	url := fmt.Sprintf("%s/servers/%s/%s", s.config.BaseURL, s.config.Alias, name)
	err := s.client.Patch(url, updates, nil)
	return err
}

func (s *Service) Delete(name string) (*status.QueuedResponse, error) {
	url := fmt.Sprintf("%s/servers/%s/%s", s.config.BaseURL, s.config.Alias, name)
	resp := &status.QueuedResponse{}
	err := s.client.Delete(url, resp)
	return resp, err
}

func (s *Service) GetCredentials(name string) (Credentials, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/credentials", s.config.BaseURL, s.config.Alias, name)
	resp := Credentials{}
	err := s.client.Get(url, &resp)
	return resp, err
}

type Credentials struct {
	Username string `json:"userName"`
	Password string `json:"password"`
}

func (s *Service) Archive(servers ...string) ([]*status.QueuedResponse, error) {
	url := fmt.Sprintf("%s/operations/%s/servers/archive", s.config.BaseURL, s.config.Alias)
	var resp []*status.QueuedResponse
	err := s.client.Post(url, servers, &resp)
	return resp, err
}

func (s *Service) Restore(name, group string) (*status.Status, error) {
	restore := map[string]string{"targetGroupId": group}
	url := fmt.Sprintf("%s/servers/%s/%s/restore", s.config.BaseURL, s.config.Alias, name)
	resp := &status.Status{}
	err := s.client.Post(url, restore, resp)
	return resp, err
}

func (s *Service) CreateSnapshot(expiration int, servers ...string) ([]*status.QueuedResponse, error) {
	snapshot := Snapshot{Expiration: expiration, Servers: servers}
	url := fmt.Sprintf("%s/operations/%s/servers/createSnapshot", s.config.BaseURL, s.config.Alias)
	var resp []*status.QueuedResponse
	err := s.client.Post(url, snapshot, &resp)
	return resp, err
}

func (s *Service) DeleteSnapshot(server, id string) (*status.Status, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/snapshots/%s", s.config.BaseURL, s.config.Alias, server, id)
	resp := &status.Status{}
	err := s.client.Delete(url, resp)
	return resp, err
}

func (s *Service) RevertSnapshot(server, id string) (*status.Status, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/snapshots/%s/restore", s.config.BaseURL, s.config.Alias, server, id)
	resp := &status.Status{}
	err := s.client.Post(url, nil, resp)
	return resp, err
}

type Snapshot struct {
	Expiration int      `json:"snapshotExpirationDays"`
	Servers    []string `json:"serverIds"`
}

func (s *Service) ExecutePackage(pkg Package, servers ...string) ([]*status.QueuedResponse, error) {
	url := fmt.Sprintf("%s/operations/%s/servers/executePackage", s.config.BaseURL, s.config.Alias)
	var resp []*status.QueuedResponse
	exec := executePackage{Servers: servers, Package: pkg}
	err := s.client.Post(url, exec, &resp)
	return resp, err
}

type executePackage struct {
	Servers []string `json:"servers"`
	Package Package  `json:"package"`
}

type Package struct {
	ID     string            `json:"packageId"`
	Params map[string]string `json:"parameters"`
}

func (s *Service) PowerState(state PowerState, servers ...string) ([]*status.QueuedResponse, error) {
	url := fmt.Sprintf("%s/operations/%s/servers/%s", s.config.BaseURL, s.config.Alias, state)
	var resp []*status.QueuedResponse
	err := s.client.Post(url, servers, &resp)
	return resp, err
}

type PowerState int

const (
	On = iota
	Off
	Pause
	Reboot
	Reset
	ShutDown
	StartMaintenance
	StopMaintenance
)

func (p PowerState) String() string {
	switch p {
	case On:
		return "powerOn"
	case Off:
		return "powerOff"
	case Pause:
		return "pause"
	case Reboot:
		return "reboot"
	case Reset:
		return "reset"
	case ShutDown:
		return "shutDown"
	case StartMaintenance:
		return "startMaintenance"
	case StopMaintenance:
		return "stopMaintenance"
	}
	return ""
}

func (s *Service) GetPublicIP(name string, ip string) (*PublicIP, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/publicIPAddresses/%s", s.config.BaseURL, s.config.Alias, name, ip)
	resp := &PublicIP{}
	err := s.client.Get(url, resp)
	return resp, err
}

func (s *Service) AddPublicIP(name string, ip PublicIP) (*status.Status, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/publicIPAddresses", s.config.BaseURL, s.config.Alias, name)
	resp := &status.Status{}
	err := s.client.Post(url, ip, resp)
	return resp, err
}

func (s *Service) UpdatePublicIP(name string, public string, ip PublicIP) (*status.Status, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/publicIPAddresses/%s", s.config.BaseURL, s.config.Alias, name, public)
	resp := &status.Status{}
	err := s.client.Put(url, ip, resp)
	return resp, err
}

func (s *Service) DeletePublicIP(name, ip string) (*status.Status, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/publicIPAddresses/%s", s.config.BaseURL, s.config.Alias, name, ip)
	resp := &status.Status{}
	err := s.client.Delete(url, resp)
	return resp, err
}

type PublicIP struct {
	InternalIP         string              `json:"internalIPAddress,omitempty"`
	Ports              []Port              `json:"ports,omitempty"`
	SourceRestrictions []SourceRestriction `json:"sourceRestrictions,omitempty"`
}

type Port struct {
	Protocol string `json:"protocol"`
	Port     int    `json:"port"`
	PortTo   int    `json:"portTo,omitempty"`
}

type SourceRestriction struct {
	CIDR string `json:"cidr"`
}

type Disk struct {
	DiskID string `json:"diskId,omitempty"`
	Path   string `json:"path,omitempty"`
	SizeGB int    `json:"sizeGB,omitempty"`
	Type   string `json:"type,omitempty"`
}

func (s *Service) AddSecondaryNetwork(name, networkId, ip string) (*status.Status, error) {
	url := fmt.Sprintf("%s/servers/%s/%s/networks", s.config.BaseURL, s.config.Alias, name)
	req := &SecondaryNetwork{
		NetworkID: networkId,
		IPAddress: ip,
	}
	// returned a non-standard status object, repackage into a proper one
	resp := &status.QueuedOperation{}
	err := s.client.Post(url, req, resp)
	if err != nil {
		return nil, err
	}
	return resp.Status(), nil
}

type SecondaryNetwork struct {
	NetworkID string `json:"networkId,omitempty"`
	IPAddress string `json:"ipAddress,omitempty"`
}

func UpdateCPU(num int) api.Update {
	return api.Update{
		Op:     "set",
		Member: "cpu",
		Value:  num,
	}
}

func UpdateMemory(num int) api.Update {
	return api.Update{
		Op:     "set",
		Member: "memory",
		Value:  num,
	}
}

func UpdateCredentials(current, updated string) api.Update {
	return api.Update{
		Op:     "set",
		Member: "password",
		Value: struct {
			Current  string `json:"current"`
			Password string `json:"password"`
		}{
			current,
			updated,
		},
	}
}

func UpdateGroup(group string) api.Update {
	return api.Update{
		Op:     "set",
		Member: "groupId",
		Value:  group,
	}
}

func UpdateDescription(desc string) api.Update {
	return api.Update{
		Op:     "set",
		Member: "description",
		Value:  desc,
	}
}

func UpdateAdditionaldisks(disks []Disk) api.Update {
	return api.Update{
		Op:     "set",
		Member: "disks",
		Value:  disks,
	}
}

func UpdateCustomfields(fields []api.Customfields) api.Update {
	return api.Update{
		Op:     "set",
		Member: "customFields",
		Value:  fields,
	}
}

type Server struct {
	Name                 string             `json:"name"`
	Description          string             `json:"description,omitempty"`
	GroupID              string             `json:"groupId"`
	SourceServerID       string             `json:"sourceServerId"`
	IsManagedOS          bool               `json:"isManagedOS,omitempty"`
	IsManagedBackup      bool               `json:"isManagedBackup,omitempty"`
	PrimaryDNS           string             `json:"primaryDns,omitempty"`
	SecondaryDNS         string             `json:"secondaryDns,omitempty"`
	NetworkID            string             `json:"networkId,omitempty"`
	IPaddress            string             `json:"ipAddress,omitempty"`
	Password             string             `json:"password,omitempty"`
	SourceServerPassword string             `json:"sourceServerPassword,omitempty"`
	CPU                  int                `json:"cpu"`
	CPUAutoscalePolicyID string             `json:"cpuAutoscalePolicyId,omitempty"`
	MemoryGB             int                `json:"memoryGB"`
	Type                 string             `json:"type"`
	Storagetype          string             `json:"storageType,omitempty"`
	AntiAffinityPolicyID string             `json:"antiAffinityPolicyId,omitempty"`
	Customfields         []api.Customfields `json:"customFields,omitempty"`
	Additionaldisks      []Disk             `json:"additionalDisks,omitempty"`
	TTL                  *time.Time         `json:"ttl,omitempty"`
	Packages             []Package          `json:"packages,omitempty"`
	ConfigurationID      string             `json:"configurationId,omitempty"`
	OSType               string             `json:"osType,omitempty"`
}

func (s *Server) Valid() bool {
	return s.Name != "" && s.CPU != 0 && s.MemoryGB != 0 && s.GroupID != "" && s.SourceServerID != "" && s.Type != ""
}

type Response struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	GroupID     string `json:"groupId"`
	IsTemplate  bool   `json:"isTemplate"`
	LocationID  string `json:"locationId"`
	OStype      string `json:"osType"`
	OS          string `json:"os"`
	Status      string `json:"status"`
	Details     struct {
		IPaddresses []struct {
			Internal string `json:"internal"`
			Public   string `json:"public"`
		} `json:"ipAddresses"`
		AlertPolicies []struct {
			ID    string    `json:"id"`
			Name  string    `json:"name"`
			Links api.Links `json:"links"`
		} `json:"alertPolicies"`
		CPU               int    `json:"cpu"`
		Diskcount         int    `json:"diskCount"`
		Hostname          string `json:"hostName"`
		InMaintenanceMode bool   `json:"inMaintenanceMode"`
		MemoryMB          int    `json:"memoryMB"`
		Powerstate        string `json:"powerState"`
		Storagegb         int    `json:"storageGB"`
		Disks             []struct {
			ID             string        `json:"id"`
			SizeGB         int           `json:"sizeGB"`
			PartitionPaths []interface{} `json:"partitionPaths"`
		} `json:"disks"`
		Partitions []struct {
			SizeGB float64 `json:"sizeGB"`
			Path   string  `json:"path"`
		} `json:"partitions"`
		Snapshots []struct {
			Name  string    `json:"name"`
			Links api.Links `json:"links"`
		} `json:"snapshots"`
		Customfields []api.Customfields `json:"customFields,omitempty"`
	} `json:"details"`
	Type        string `json:"type"`
	Storagetype string `json:"storageType"`
	ChangeInfo  struct {
		CreatedDate  string `json:"createdDate"`
		CreatedBy    string `json:"createdBy"`
		ModifiedDate string `json:"modifiedDate"`
		ModifiedBy   string `json:"modifiedBy"`
	} `json:"changeInfo"`
	Links api.Links `json:"links"`
}
