package oci

import (
	"bytes"
	"encoding/json"
	"github.com/google/go-querystring/query"
	"net/http"
	"net/url"
)

const (
	contentType     = "Content-Type"
	jsonContentType = "application/json"
)

// baseClient provides a basic (AND INTENTIONALLY INCOMPLETE) JSON REST client
// that abstracts away some of the repetitive code required in the OCI Client.
type baseClient struct {
	httpClient  *http.Client
	method      string
	url         string
	queryStruct interface{}
	header      http.Header
	body        interface{}
}

// newBaseClient constructs a default baseClient.
func newBaseClient() *baseClient {
	return &baseClient{
		httpClient: http.DefaultClient,
		method:     "GET",
		header:     make(http.Header),
	}
}

// New creates a copy of an existing baseClient.
func (c *baseClient) New() *baseClient {
	// Copy headers
	header := make(http.Header)
	for k, v := range c.header {
		header[k] = v
	}

	return &baseClient{
		httpClient: c.httpClient,
		method:     c.method,
		url:        c.url,
		header:     header,
	}
}

// Client sets the http Client used to perform requests.
func (c *baseClient) Client(httpClient *http.Client) *baseClient {
	if httpClient == nil {
		c.httpClient = http.DefaultClient
	} else {
		c.httpClient = httpClient
	}
	return c
}

// Base sets the base client url.
func (c *baseClient) Base(path string) *baseClient {
	c.url = path
	return c
}

// Path extends the client url.
func (c *baseClient) Path(path string) *baseClient {
	baseURL, baseErr := url.Parse(c.url)
	pathURL, pathErr := url.Parse(path)
	// Bail on parsing error leaving the client's url unmodified
	if baseErr != nil || pathErr != nil {
		return c
	}

	c.url = baseURL.ResolveReference(pathURL).String()
	return c
}

// QueryStruct sets the struct from which the request querystring is built.
func (c *baseClient) QueryStruct(params interface{}) *baseClient {
	c.queryStruct = params
	return c
}

// SetBody wraps a given struct for serialisation and sets the client body.
func (c *baseClient) SetBody(params interface{}) *baseClient {
	c.body = params
	return c
}

// Header

// AddHeader adds a HTTP header to the client. Existing keys will be extended.
func (c *baseClient) AddHeader(key, value string) *baseClient {
	c.header.Add(key, value)
	return c
}

// SetHeader sets a HTTP header on the client. Existing keys will be
// overwritten.
func (c *baseClient) SetHeader(key, value string) *baseClient {
	c.header.Add(key, value)
	return c
}

// HTTP methods (subset)

// Get sets the client's HTTP method to GET.
func (c *baseClient) Get(path string) *baseClient {
	c.method = "GET"
	return c.Path(path)
}

// Post sets the client's HTTP method to POST.
func (c *baseClient) Post(path string) *baseClient {
	c.method = "POST"
	return c.Path(path)
}

// Delete sets the client's HTTP method to DELETE.
func (c *baseClient) Delete(path string) *baseClient {
	c.method = "DELETE"
	return c.Path(path)
}

// Do executes a HTTP request and returns the response encoded as either error
// or success values.
func (c *baseClient) Do(req *http.Request, successV, failureV interface{}) (*http.Response, error) {
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		if successV != nil {
			err = json.NewDecoder(resp.Body).Decode(successV)
		}
	} else {
		if failureV != nil {
			err = json.NewDecoder(resp.Body).Decode(failureV)
		}
	}

	return resp, err
}

// Request builds a http.Request from the baseClient instance.
func (c *baseClient) Request() (*http.Request, error) {
	reqURL, err := url.Parse(c.url)
	if err != nil {
		return nil, err
	}

	if c.queryStruct != nil {
		err = addQueryStruct(reqURL, c.queryStruct)
		if err != nil {
			return nil, err
		}
	}

	body := &bytes.Buffer{}
	if c.body != nil {
		if err := json.NewEncoder(body).Encode(c.body); err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(c.method, reqURL.String(), body)
	if err != nil {
		return nil, err
	}

	// Add headers to request
	for k, vs := range c.header {
		for _, v := range vs {
			req.Header.Add(k, v)
		}
	}

	return req, nil
}

// Recieve creates a http request from the client and executes it returning the
// response.
func (c *baseClient) Receive(successV, failureV interface{}) (*http.Response, error) {
	req, err := c.Request()
	if err != nil {
		return nil, err
	}
	return c.Do(req, successV, failureV)
}

// addQueryStruct converts a struct to a querystring and merges any values
// provided in the URL itself.
func addQueryStruct(reqURL *url.URL, queryStruct interface{}) error {
	urlValues, err := url.ParseQuery(reqURL.RawQuery)
	if err != nil {
		return err
	}
	queryValues, err := query.Values(queryStruct)
	if err != nil {
		return err
	}

	for k, vs := range queryValues {
		for _, v := range vs {
			urlValues.Add(k, v)
		}
	}
	reqURL.RawQuery = urlValues.Encode()
	return nil
}
