package src

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

	"github.com/pkg/errors"
)

//Client struct
type Client struct {
	token      string
	basePath   string
	HTTPClient *http.Client
}

//NewClient creates new client
func NewClient(token string, client ...*http.Client) *Client {
	return newClientInternal(
		token,
		"https://cloud-api.yandex.com/v1/disk", //also "https://cloud-api.yandex.net/v1/disk" "https://cloud-api.yandex.ru/v1/disk"
		client...)
}

func newClientInternal(token string, basePath string, client ...*http.Client) *Client {
	c := &Client{
		token:    token,
		basePath: basePath,
	}
	if len(client) != 0 {
		c.HTTPClient = client[0]
	} else {
		c.HTTPClient = http.DefaultClient
	}
	return c
}

//ErrorHandler type
type ErrorHandler func(*http.Response) error

var defaultErrorHandler ErrorHandler = func(resp *http.Response) error {
	if resp.StatusCode/100 == 5 {
		return errors.New("server error")
	}

	if resp.StatusCode/100 == 4 {
		var response DiskClientError
		contents, _ := ioutil.ReadAll(resp.Body)
		err := json.Unmarshal(contents, &response)
		if err != nil {
			return err
		}
		return response
	}

	if resp.StatusCode/100 == 3 {
		return errors.New("redirect error")
	}
	return nil
}

func (HTTPRequest *HTTPRequest) run(client *Client) ([]byte, error) {
	var err error
	values := make(url.Values)
	if HTTPRequest.Parameters != nil {
		for k, v := range HTTPRequest.Parameters {
			values.Set(k, fmt.Sprintf("%v", v))
		}
	}

	var req *http.Request
	if HTTPRequest.Method == "POST" {
		// TODO json serialize
		req, err = http.NewRequest(
			"POST",
			client.basePath+HTTPRequest.Path,
			strings.NewReader(values.Encode()))
		if err != nil {
			return nil, err
		}
		// TODO
		// req.Header.Set("Content-Type", "application/json")
	} else {
		req, err = http.NewRequest(
			HTTPRequest.Method,
			client.basePath+HTTPRequest.Path+"?"+values.Encode(),
			nil)
		if err != nil {
			return nil, err
		}
	}

	for headerName := range HTTPRequest.Headers {
		var headerValues = HTTPRequest.Headers[headerName]
		for _, headerValue := range headerValues {
			req.Header.Set(headerName, headerValue)
		}
	}
	return runRequest(client, req)
}

func runRequest(client *Client, req *http.Request) ([]byte, error) {
	return runRequestWithErrorHandler(client, req, defaultErrorHandler)
}

func runRequestWithErrorHandler(client *Client, req *http.Request, errorHandler ErrorHandler) (out []byte, err error) {
	resp, err := client.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer CheckClose(resp.Body, &err)

	return checkResponseForErrorsWithErrorHandler(resp, errorHandler)
}

func checkResponseForErrorsWithErrorHandler(resp *http.Response, errorHandler ErrorHandler) ([]byte, error) {
	if resp.StatusCode/100 > 2 {
		return nil, errorHandler(resp)
	}
	return ioutil.ReadAll(resp.Body)
}

// CheckClose is a utility function used to check the return from
// Close in a defer statement.
func CheckClose(c io.Closer, err *error) {
	cerr := c.Close()
	if *err == nil {
		*err = cerr
	}
}