package civogo

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

// Database holds the database information
type Database struct {
	ID              string `json:"id"`
	Name            string `json:"name"`
	Nodes           int    `json:"nodes"`
	Size            string `json:"size"`
	Software        string `json:"software"`
	SoftwareVersion string `json:"software_version"`
	PublicIPv4      string `json:"public_ipv4"`
	NetworkID       string `json:"network_id"`
	FirewallID      string `json:"firewall_id"`
	Port            int    `json:"port"`
	Username        string `json:"username"`
	Password        string `json:"password"`
	Status          string `json:"status"`
}

// PaginatedDatabases is the structure for list response from DB endpoint
type PaginatedDatabases struct {
	Page    int        `json:"page"`
	PerPage int        `json:"per_page"`
	Pages   int        `json:"pages"`
	Items   []Database `json:"items"`
}

// CreateDatabaseRequest holds fields required to creates a new database
type CreateDatabaseRequest struct {
	Name            string `json:"name" validate:"required"`
	Size            string `json:"size" validate:"required"`
	Software        string `json:"software" validate:"required"`
	SoftwareVersion string `json:"software_version"`
	NetworkID       string `json:"network_id"`
	Nodes           int    `json:"nodes"`
	FirewallID      string `json:"firewall_id"`
	FirewallRules   string `json:"firewall_rule"`
	Region          string `json:"region"`
}

// UpdateDatabaseRequest holds fields required to update a database
type UpdateDatabaseRequest struct {
	Name       string `json:"name"`
	Nodes      *int   `json:"nodes"`
	FirewallID string `json:"firewall_id"`
	Region     string `json:"region"`
}

// SupportedSoftwareVersion contains the information related to a specific software version
type SupportedSoftwareVersion struct {
	SoftwareVersion string `json:"software_version"`
	Default         bool   `json:"default"`
}

// ListDatabases returns a list of all databases
func (c *Client) ListDatabases() (*PaginatedDatabases, error) {
	resp, err := c.SendGetRequest("/v2/databases")
	if err != nil {
		return nil, decodeError(err)
	}

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

	return databases, nil
}

// GetDatabase finds a database by the database UUID
func (c *Client) GetDatabase(id string) (*Database, error) {
	resp, err := c.SendGetRequest(fmt.Sprintf("/v2/databases/%s", id))
	if err != nil {
		return nil, decodeError(err)
	}

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

	return db, nil
}

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

	return c.DecodeSimpleResponse(resp)
}

// NewDatabase creates a new database
func (c *Client) NewDatabase(v *CreateDatabaseRequest) (*Database, error) {
	body, err := c.SendPostRequest("/v2/databases", v)
	if err != nil {
		return nil, decodeError(err)
	}

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

	return result, nil
}

// UpdateDatabase updates a database
func (c *Client) UpdateDatabase(id string, v *UpdateDatabaseRequest) (*Database, error) {
	body, err := c.SendPutRequest(fmt.Sprintf("/v2/databases/%s", id), v)
	if err != nil {
		return nil, decodeError(err)
	}

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

	return result, nil
}

// FindDatabase finds a database by either part of the ID or part of the name
func (c *Client) FindDatabase(search string) (*Database, error) {
	databases, err := c.ListDatabases()
	if err != nil {
		return nil, decodeError(err)
	}

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

	for _, value := range databases.Items {
		if strings.EqualFold(value.Name, search) || value.ID == search {
			exactMatch = true
			result = value
		} else if strings.Contains(strings.ToUpper(value.Name), strings.ToUpper(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)
	}
}

// ListDBVersions returns a list of all database versions
func (c *Client) ListDBVersions() (map[string][]SupportedSoftwareVersion, error) {
	resp, err := c.SendGetRequest("/v2/databases/versions")
	if err != nil {
		return nil, decodeError(err)
	}

	versions := make(map[string][]SupportedSoftwareVersion, 0)
	if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&versions); err != nil {
		return nil, err
	}

	return versions, nil
}
