package rest

import (
	"bytes"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/Jeffail/gabs"
)

// Method contains the supported HTTP verbs.
type Method string

// Supported HTTP verbs.
const (
	Get    Method = "GET"
	Post   Method = "POST"
	Put    Method = "PUT"
	Patch  Method = "PATCH"
	Delete Method = "DELETE"
)

// Request holds the request to an API Call.
type Request struct {
	Method      Method
	BaseURL     string // e.g. https://api.service.com
	Headers     map[string]string
	QueryParams map[string]string
	Body        []byte
}

// Response holds the response from an API call.
type Response struct {
	StatusCode int         // e.g. 200
	Headers    http.Header // e.g. map[X-Rate-Limit:[600]]
	Body       string      // e.g. {"result: success"}
	JSON       *gabs.Container
}

// ParseJSON parses the response body to JSON container.
func (r *Response) ParseJSON() error {
	if strings.Contains(r.Headers.Get("Content-Type"), "application/json") {
		json, err := gabs.ParseJSON([]byte(r.Body))
		if err != nil {
			return err
		}

		r.JSON = json
		return nil
	}

	return errors.New("response body is not JSON")
}

// DefaultClient is used if no custom HTTP client is defined
var DefaultClient = &Client{HTTPClient: http.DefaultClient}

// Client allows modification of client headers, redirect policy
// and other settings
// See https://golang.org/pkg/net/http
type Client struct {
	HTTPClient *http.Client
}

// The following functions enable the ability to define a
// custom HTTP Client

// MakeRequest makes the API call.
func (c *Client) MakeRequest(req *http.Request) (*http.Response, error) {
	return c.HTTPClient.Do(req)
}

// API is the main interface to the API.
func (c *Client) API(r *Request) (*Response, error) {
	// Build the HTTP request object.
	req, err := BuildRequestObject(r)
	if err != nil {
		return nil, err
	}

	// Build the HTTP client and make the request.
	res, err := c.MakeRequest(req)
	if err != nil {
		return nil, err
	}

	// Build Response object.
	response, err := BuildResponse(res)
	if err != nil {
		return nil, err
	}

	return response, nil
}

// AddQueryParameters adds query parameters to the URL.
func AddQueryParameters(baseURL string, queryParams map[string]string) string {
	baseURL += "?"
	params := url.Values{}
	for key, value := range queryParams {
		params.Add(key, value)
	}
	return baseURL + params.Encode()
}

// BuildRequestObject creates the HTTP request object.
func BuildRequestObject(r *Request) (*http.Request, error) {
	// Add any query parameters to the URL.
	if len(r.QueryParams) != 0 {
		r.BaseURL = AddQueryParameters(r.BaseURL, r.QueryParams)
	}
	req, err := http.NewRequest(string(r.Method), r.BaseURL, bytes.NewBuffer(r.Body))
	for key, value := range r.Headers {
		req.Header.Set(key, value)
	}
	_, exists := req.Header["Content-Type"]
	if len(r.Body) > 0 && !exists {
		req.Header.Set("Content-Type", "application/json")
	}
	return req, err
}

// BuildResponse builds the response struct.
func BuildResponse(r *http.Response) (*Response, error) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()

	response := Response{
		StatusCode: r.StatusCode,
		Body:       string(body),
		Headers:    r.Header,
	}

	return &response, nil
}

// MakeRequest makes the API call.
func MakeRequest(r *http.Request) (*http.Response, error) {
	return DefaultClient.HTTPClient.Do(r)
}

// API is the main interface to the API.
func API(request *Request) (*Response, error) {
	return DefaultClient.API(request)
}