package cfclient

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

	"github.com/pkg/errors"
)

type ServiceKeysResponse struct {
	Count     int                  `json:"total_results"`
	Pages     int                  `json:"total_pages"`
	Resources []ServiceKeyResource `json:"resources"`
	NextUrl   string               `json:"next_url"`
}

type ServiceKeyResource struct {
	Meta   Meta       `json:"metadata"`
	Entity ServiceKey `json:"entity"`
}

type CreateServiceKeyRequest struct {
	Name                string      `json:"name"`
	ServiceInstanceGuid string      `json:"service_instance_guid"`
	Parameters          interface{} `json:"parameters,omitempty"`
}

type ServiceKey struct {
	Name                string      `json:"name"`
	Guid                string      `json:"guid"`
	CreatedAt           string      `json:"created_at"`
	UpdatedAt           string      `json:"updated_at"`
	ServiceInstanceGuid string      `json:"service_instance_guid"`
	Credentials         interface{} `json:"credentials"`
	ServiceInstanceUrl  string      `json:"service_instance_url"`
	c                   *Client
}

func (c *Client) ListServiceKeysByQuery(query url.Values) ([]ServiceKey, error) {
	var serviceKeys []ServiceKey
	requestUrl := "/v2/service_keys?" + query.Encode()

	for {
		var serviceKeysResp ServiceKeysResponse

		r := c.NewRequest("GET", requestUrl)
		resp, err := c.DoRequest(r)
		if err != nil {
			return nil, errors.Wrap(err, "Error requesting service keys")
		}
		defer resp.Body.Close()
		resBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, errors.Wrap(err, "Error reading service keys request:")
		}

		err = json.Unmarshal(resBody, &serviceKeysResp)
		if err != nil {
			return nil, errors.Wrapf(err, "Error unmarshaling service keys: %q", string(resBody))
		}
		for _, serviceKey := range serviceKeysResp.Resources {
			serviceKey.Entity.Guid = serviceKey.Meta.Guid
			serviceKey.Entity.CreatedAt = serviceKey.Meta.CreatedAt
			serviceKey.Entity.UpdatedAt = serviceKey.Meta.UpdatedAt
			serviceKey.Entity.c = c
			serviceKeys = append(serviceKeys, serviceKey.Entity)
		}

		requestUrl = serviceKeysResp.NextUrl
		if requestUrl == "" {
			break
		}
	}

	return serviceKeys, nil
}

func (c *Client) GetServiceKeyByGuid(guid string) (ServiceKey, error) {
	var serviceKey ServiceKeyResource
	r := c.NewRequest("GET", "/v2/service_keys/"+url.QueryEscape(guid))
	resp, err := c.DoRequest(r)
	if err != nil {
		return ServiceKey{}, errors.Wrap(err, "Error requesting serving Key")
	}
	defer resp.Body.Close()
	resBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ServiceKey{}, errors.Wrap(err, "Error reading service Key response body")
	}
	err = json.Unmarshal(resBody, &serviceKey)
	if err != nil {
		return ServiceKey{}, errors.Wrap(err, "Error unmarshalling service Key")
	}
	serviceKey.Entity.Guid = serviceKey.Meta.Guid
	serviceKey.Entity.c = c
	return serviceKey.Entity, nil
}

func (c *Client) ListServiceKeys() ([]ServiceKey, error) {
	return c.ListServiceKeysByQuery(nil)
}

func (c *Client) GetServiceKeyByName(name string) (ServiceKey, error) {
	var serviceKey ServiceKey
	q := url.Values{}
	q.Set("q", "name:"+name)
	serviceKeys, err := c.ListServiceKeysByQuery(q)
	if err != nil {
		return serviceKey, err
	}
	if len(serviceKeys) == 0 {
		cfErr := NewServiceKeyNotFoundError()
		cfErr.Description = fmt.Sprintf(cfErr.Description, name)
		return ServiceKey{}, cfErr
	}
	return serviceKeys[0], nil
}

// GetServiceKeyByInstanceGuid is deprecated in favor of GetServiceKeysByInstanceGuid
func (c *Client) GetServiceKeyByInstanceGuid(guid string) (ServiceKey, error) {
	q := url.Values{}
	q.Set("q", "service_instance_guid:"+guid)
	serviceKeys, err := c.ListServiceKeysByQuery(q)
	if err != nil {
		return ServiceKey{}, err
	}
	if len(serviceKeys) == 0 {
		cfErr := NewServiceKeyNotFoundError()
		return ServiceKey{}, cfErr
	}
	return serviceKeys[0], nil
}

// GetServiceKeysByInstanceGuid returns the service keys for a service instance.
// If none are found, it returns an error.
func (c *Client) GetServiceKeysByInstanceGuid(guid string) ([]ServiceKey, error) {
	q := url.Values{}
	q.Set("q", "service_instance_guid:"+guid)
	serviceKeys, err := c.ListServiceKeysByQuery(q)
	if err != nil {
		return serviceKeys, err
	}
	if len(serviceKeys) == 0 {
		cfErr := NewServiceKeyNotFoundError()
		return serviceKeys, cfErr
	}
	return serviceKeys, nil
}

// CreateServiceKey creates a service key from the request. If a service key
// exists already, it returns an error containing `CF-ServiceKeyNameTaken`
func (c *Client) CreateServiceKey(csr CreateServiceKeyRequest) (ServiceKey, error) {
	var serviceKeyResource ServiceKeyResource

	buf := bytes.NewBuffer(nil)
	err := json.NewEncoder(buf).Encode(csr)
	if err != nil {
		return ServiceKey{}, err
	}
	req := c.NewRequestWithBody("POST", "/v2/service_keys", buf)
	resp, err := c.DoRequest(req)
	if err != nil {
		return ServiceKey{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusCreated {
		return ServiceKey{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ServiceKey{}, err
	}
	err = json.Unmarshal(body, &serviceKeyResource)
	if err != nil {
		return ServiceKey{}, err
	}

	return c.mergeServiceKey(serviceKeyResource), nil
}

// DeleteServiceKey removes a service key instance
func (c *Client) DeleteServiceKey(guid string) error {
	resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/service_keys/%s", guid)))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusNoContent {
		return errors.Wrapf(err, "Error deleting service instance key %s, response code %d", guid, resp.StatusCode)
	}
	return nil
}

func (c *Client) mergeServiceKey(key ServiceKeyResource) ServiceKey {
	key.Entity.Guid = key.Meta.Guid
	key.Entity.CreatedAt = key.Meta.CreatedAt
	key.Entity.UpdatedAt = key.Meta.UpdatedAt
	key.Entity.c = c
	return key.Entity
}
