package crossregion

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

// Add tracing header
// https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/net/http/otelhttp/v0.40.0/instrumentation/net/http/otelhttp/client.go#L31

type CrossRegionClient struct {
	envType    EnvType
	authType   AuthType
	authKey    string
	httpClient *http.Client
}

func NewBasicAuthClient(envType EnvType, authKey string, timeoutSec time.Duration) *CrossRegionClient {
	return &CrossRegionClient{
		envType:    envType,
		authType:   BasicAuth,
		authKey:    authKey,
		httpClient: &http.Client{Timeout: timeoutSec, Transport: otelhttp.NewTransport(http.DefaultTransport)},
	}
}

func (client *CrossRegionClient) RequestGlobal(ctx context.Context, method string, url string, queryString *map[string]string, header *map[string]string, payload interface{}) (string, int, error) {
	globalDomain, err := GetGlobalInnerDomain(client.envType)
	if err != nil {
		return "", 0, err
	}
	globalUrl := globalDomain + url
	return client.requestWithHeader(ctx, method, globalUrl, queryString, header, payload)
}

func (client *CrossRegionClient) PostGlobal(ctx context.Context, url string, data interface{}) (string, int, error) {
	globalDomain, err := GetGlobalInnerDomain(client.envType)
	if err != nil {
		return "", 0, err
	}
	globalUrl := globalDomain + url
	return client.postWithHeader(ctx, globalUrl, data, nil)
}

func (client *CrossRegionClient) PostO11yRegion(ctx context.Context, region string, url string, data interface{}) (string, int, error) {
	globalDomain, err := GetO11yRegionInnerDomain(client.envType, region)
	if err != nil {
		return "", 0, err
	}
	globalUrl := globalDomain + url
	return client.postWithHeader(ctx, globalUrl, data, nil)
}

func (client *CrossRegionClient) GetGlobal(ctx context.Context, url string, data *map[string]string) (string, int, error) {
	globalDomain, err := GetGlobalInnerDomain(client.envType)
	if err != nil {
		return "", 0, err
	}
	globalUrl := globalDomain + url
	return client.getWithHeader(ctx, globalUrl, data, nil)
}

func (client *CrossRegionClient) GetO11yRegion(ctx context.Context, region string, url string, data *map[string]string) (string, int, error) {
	globalDomain, err := GetO11yRegionInnerDomain(client.envType, region)
	if err != nil {
		return "", 0, err
	}
	globalUrl := globalDomain + url
	return client.getWithHeader(ctx, globalUrl, data, nil)
}

func encodeJSON(data interface{}) io.Reader {
	b, _ := json.Marshal(data)
	return bytes.NewReader(b)
}

// Get request by url
func (client *CrossRegionClient) postWithHeader(ctx context.Context, url string, data interface{}, header *map[string]string) (string, int, error) {
	return client.requestWithHeader(ctx, http.MethodPost, url, nil, header, data)
}

// Get request by url
func (client *CrossRegionClient) getWithHeader(ctx context.Context, url string, params *map[string]string, header *map[string]string) (string, int, error) {
	return client.requestWithHeader(ctx, http.MethodGet, url, params, header, nil)
}

func (client *CrossRegionClient) requestWithHeader(ctx context.Context, method string, url string, queryString *map[string]string, header *map[string]string, payload interface{}) (string, int, error) {
	var body io.Reader
	if payload != nil {
		body = encodeJSON(payload)
	}

	req, err := http.NewRequestWithContext(ctx, method, url, body)
	if err != nil {
		return "", 0, err
	}
	if header != nil {
		for k, v := range *header {
			req.Header.Set(k, v)
		}
	}
	req.Header.Set("Authorization", "Basic "+client.authKey)
	if method != http.MethodGet {
		req.Header.Set("Content-Type", "application/json")
	}

	if queryString != nil {
		q := req.URL.Query()
		for k, v := range *queryString {
			q.Add(k, v)
		}
		req.URL.RawQuery = q.Encode()
	}
	resp, err := client.httpClient.Do(req)
	if nil != err {
		return "", 0, err
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	return string(data), resp.StatusCode, err
}
