package api

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

type ClientOptions struct {
	Url          string
	AccessToken  string
	RefreshToken string
}

type Client struct {
	options *ClientOptions
	BaseUrl *url.URL
}

type requestOptions struct {
	HttpMethod string
	Body       interface{}
	Headers    map[string]string
}

type OauthTokenResponse struct {
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
}

func NewClient(options *ClientOptions) (*Client, error) {
	if len(options.AccessToken) == 0 || len(options.RefreshToken) == 0 || len(options.Url) == 0 {
		return nil, errors.New("AmoCrm: Invalid options")
	}

	resolvedUrl, err := url.Parse(options.Url)
	if err != nil {
		return nil, err
	}

	return &Client{
		options: options,
		BaseUrl: resolvedUrl,
	}, nil
}

func (api *Client) doRequest(resourceUrl string, requestParams requestOptions, result interface{}) error {
	resolvedUrl, err := url.Parse(resourceUrl)
	if err != nil {
		return err
	}

	requestUrl := api.BaseUrl.ResolveReference(resolvedUrl)

	requestBody := new(bytes.Buffer)
	if requestParams.Body != nil {
		encoder := json.NewEncoder(requestBody)
		if err := encoder.Encode(requestParams.Body); err != nil {
			return err
		}
	}

	request, err := http.NewRequest(requestParams.HttpMethod, requestUrl.String(), requestBody)
	if err != nil {
		return err
	}

	if requestParams.Headers != nil {
		for k, v := range requestParams.Headers {
			request.Header.Set(k, v)
		}
	}

	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return errors.New(fmt.Sprintf("Request error: %s %d %s %s", err.Error(), response.StatusCode, requestParams.HttpMethod, resourceUrl))
	}
	defer response.Body.Close()

	if response.StatusCode >= 400 {
		bodyBytes, _ := ioutil.ReadAll(response.Body)
		return errors.New(fmt.Sprintf("%s %d %s %s", string(bodyBytes), response.StatusCode, requestParams.HttpMethod, resourceUrl))
	}

	if result != nil {
		decoder := json.NewDecoder(response.Body)
		err = decoder.Decode(result)
		if err != nil {
			return err
		}
	}

	return nil
}

func (api *Client) RefreshToken() (*map[string]interface{}, error) {
	result := new(map[string]interface{})
	request := map[string]string{
		"client_id":     "7c08f8a9-c49d-4378-890c-a6dba79f88f9",
		"client_secret": "SDqfNBxxk98zq6CRjLN7tHeyVHS0EAwlQkirAx0i71s81N9fXGPlTlkoxIOZd6Rg",
		"grant_type":    "refresh_token",
		"refresh_token": api.options.RefreshToken,
		"redirect_uri":  "https://ddamosmartheadru.amocrm.ru",
	}

	err := api.doRequest("/oauth2/access_token", requestOptions{
		HttpMethod: http.MethodPost,
		Body:       request,
		Headers: map[string]string{
			"Accept":        "application/json",
			"Cache-Control": "no-cache",
			"Content-Type":  "application/json",
		},
	}, result)

	return result, err
}

func (api *Client) Get(resource string, result interface{}) error {
	return api.doRequest(resource, requestOptions{
		HttpMethod: http.MethodGet,
		Body:       nil,
		Headers: map[string]string{
			"Accept":        "application/json",
			"Cache-Control": "no-cache",
			"Content-Type":  "application/json",
			"Authorization": fmt.Sprintf("Bearer %s", api.options.AccessToken),
		},
	}, result)
}

func (api *Client) Post(resource string, request interface{}, result interface{}) error {
	return api.doRequest(resource, requestOptions{
		HttpMethod: http.MethodPost,
		Body:       request,
		Headers: map[string]string{
			"Accept":        "application/json",
			"Cache-Control": "no-cache",
			"Content-Type":  "application/json",
			"Authorization": fmt.Sprintf("Bearer %s", api.options.AccessToken),
		},
	}, result)
}

func (api *Client) Patch(resource string, request interface{}, result interface{}) error {
	return api.doRequest(resource, requestOptions{
		HttpMethod: http.MethodPatch,
		Body:       request,
		Headers: map[string]string{
			"Accept":        "application/json",
			"Cache-Control": "no-cache",
			"Content-Type":  "application/json",
			"Authorization": fmt.Sprintf("Bearer %s", api.options.AccessToken),
		},
	}, result)
}