package civogo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
)

// LoadBalancerBackend represents a backend instance being load-balanced
type LoadBalancerBackend struct {
	IP              string `json:"ip"`
	Protocol        string `json:"protocol,omitempty"`
	SourcePort      int32  `json:"source_port"`
	TargetPort      int32  `json:"target_port"`
	HealthCheckPort int32  `json:"health_check_port,omitempty"`
}

// LoadBalancerBackendConfig is the configuration for creating backends
type LoadBalancerBackendConfig struct {
	IP              string `json:"ip"`
	Protocol        string `json:"protocol,omitempty"`
	SourcePort      int32  `json:"source_port"`
	TargetPort      int32  `json:"target_port"`
	HealthCheckPort int32  `json:"health_check_port,omitempty"`
}

// LoadBalancer represents a load balancer configuration within Civo
type LoadBalancer struct {
	ID                           string                `json:"id"`
	Name                         string                `json:"name"`
	Algorithm                    string                `json:"algorithm"`
	Backends                     []LoadBalancerBackend `json:"backends"`
	ExternalTrafficPolicy        string                `json:"external_traffic_policy,omitempty"`
	SessionAffinity              string                `json:"session_affinity,omitempty"`
	SessionAffinityConfigTimeout int32                 `json:"session_affinity_config_timeout,omitempty"`
	EnableProxyProtocol          string                `json:"enable_proxy_protocol,omitempty"`
	PublicIP                     string                `json:"public_ip"`
	PrivateIP                    string                `json:"private_ip"`
	FirewallID                   string                `json:"firewall_id"`
	ClusterID                    string                `json:"cluster_id,omitempty"`
	State                        string                `json:"state"`
	ReservedIPID                 string                `json:"reserved_ip_id,omitempty"`
	ReservedIPName               string                `json:"reserved_ip_name,omitempty"`
	ReservedIP                   string                `json:"reserved_ip,omitempty"`
}

// LoadBalancerConfig represents a load balancer to be created
type LoadBalancerConfig struct {
	Region                       string                      `json:"region"`
	Name                         string                      `json:"name"`
	NetworkID                    string                      `json:"network_id,omitempty"`
	Algorithm                    string                      `json:"algorithm,omitempty"`
	Backends                     []LoadBalancerBackendConfig `json:"backends"`
	ExternalTrafficPolicy        string                      `json:"external_traffic_policy,omitempty"`
	SessionAffinity              string                      `json:"session_affinity,omitempty"`
	SessionAffinityConfigTimeout int32                       `json:"session_affinity_config_timeout,omitempty"`
	EnableProxyProtocol          string                      `json:"enable_proxy_protocol,omitempty"`
	ClusterID                    string                      `json:"cluster_id,omitempty"`
	FirewallID                   string                      `json:"firewall_id,omitempty"`
	FirewallRules                string                      `json:"firewall_rule,omitempty"`
}

// LoadBalancerUpdateConfig represents a load balancer to be updated
type LoadBalancerUpdateConfig struct {
	Region                       string                      `json:"region"`
	Name                         string                      `json:"name,omitempty"`
	Algorithm                    string                      `json:"algorithm,omitempty"`
	Backends                     []LoadBalancerBackendConfig `json:"backends,omitempty"`
	ExternalTrafficPolicy        string                      `json:"external_traffic_policy,omitempty"`
	SessionAffinity              string                      `json:"session_affinity,omitempty"`
	SessionAffinityConfigTimeout int32                       `json:"session_affinity_config_timeout,omitempty"`
	EnableProxyProtocol          string                      `json:"enable_proxy_protocol,omitempty"`
	FirewallID                   string                      `json:"firewall_id,omitempty"`
}

// ListLoadBalancers returns all load balancers owned by the calling API account
func (c *Client) ListLoadBalancers() ([]LoadBalancer, error) {
	resp, err := c.SendGetRequest("/v2/loadbalancers")
	if err != nil {
		return nil, decodeError(err)
	}

	loadbalancer := make([]LoadBalancer, 0)
	if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&loadbalancer); err != nil {
		return nil, decodeError(err)
	}

	return loadbalancer, nil
}

// GetLoadBalancer returns a load balancer
func (c *Client) GetLoadBalancer(id string) (*LoadBalancer, error) {
	resp, err := c.SendGetRequest(fmt.Sprintf("/v2/loadbalancers/%s", id))
	if err != nil {
		return nil, decodeError(err)
	}

	loadbalancer := &LoadBalancer{}
	if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&loadbalancer); err != nil {
		return nil, decodeError(err)
	}

	return loadbalancer, nil
}

// FindLoadBalancer finds a load balancer by either part of the ID or part of the name
func (c *Client) FindLoadBalancer(search string) (*LoadBalancer, error) {
	lbs, err := c.ListLoadBalancers()
	if err != nil {
		return nil, decodeError(err)
	}

	exactMatch := false
	partialMatchesCount := 0
	result := LoadBalancer{}

	for _, value := range lbs {
		if value.Name == search || value.ID == search {
			exactMatch = true
			result = value
		} else if strings.Contains(value.Name, search) || strings.Contains(value.ID, search) {
			if !exactMatch {
				result = value
				partialMatchesCount++
			}
		}
	}

	if exactMatch || partialMatchesCount == 1 {
		return &result, nil
	} else if partialMatchesCount > 1 {
		err := fmt.Errorf("unable to find %s because there were multiple matches", search)
		return nil, MultipleMatchesError.wrap(err)
	} else {
		err := fmt.Errorf("unable to find %s, zero matches", search)
		return nil, ZeroMatchesError.wrap(err)
	}
}

// CreateLoadBalancer creates a new load balancer
func (c *Client) CreateLoadBalancer(r *LoadBalancerConfig) (*LoadBalancer, error) {
	body, err := c.SendPostRequest("/v2/loadbalancers", r)
	if err != nil {
		return nil, decodeError(err)
	}

	loadbalancer := &LoadBalancer{}
	if err := json.NewDecoder(bytes.NewReader(body)).Decode(loadbalancer); err != nil {
		return nil, err
	}

	return loadbalancer, nil
}

// UpdateLoadBalancer updates a load balancer
func (c *Client) UpdateLoadBalancer(id string, r *LoadBalancerUpdateConfig) (*LoadBalancer, error) {
	body, err := c.SendPutRequest(fmt.Sprintf("/v2/loadbalancers/%s", id), r)
	if err != nil {
		return nil, decodeError(err)
	}

	loadbalancer := &LoadBalancer{}
	if err := json.NewDecoder(bytes.NewReader(body)).Decode(loadbalancer); err != nil {
		return nil, err
	}

	return loadbalancer, nil
}

// DeleteLoadBalancer deletes a load balancer
func (c *Client) DeleteLoadBalancer(id string) (*SimpleResponse, error) {
	resp, err := c.SendDeleteRequest(fmt.Sprintf("/v2/loadbalancers/%s", id))
	if err != nil {
		return nil, decodeError(err)
	}

	return c.DecodeSimpleResponse(resp)
}
