package pdapi

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/pkg/errors"
	"github.com/tidbcloud/config-service/pkg/auth"
	"github.com/tidbcloud/config-service/pkg/utils"
	"k8s.io/apimachinery/pkg/util/json"
)

var (
	configPrefix = "pd/api/v1/config"
)

type PDAuth func(*http.Request) error

func NewAuth0PDAuth(domain, clientID, auth0ClientSecret, auth0Audience string) PDAuth {
	return func(r *http.Request) error {
		token, err := auth.GetClientToken(domain, clientID, auth0ClientSecret, auth0Audience)
		if err != nil {
			return err
		}
		r.Header.Add("Authorization", fmt.Sprintf("bearer %s", token))
		return nil
	}
}

type DynamicPDClient interface {
	GetConfig() (map[string]interface{}, error)
	PutConfig(conf map[string]interface{}) error
}

func New(url string, client *http.Client, auth PDAuth) DynamicPDClient {
	return &pdClient{
		url:    url,
		client: client,
		auth:   auth,
	}
}

type pdClient struct {
	url    string
	client *http.Client
	auth   PDAuth
}

func (c *pdClient) GetConfig() (map[string]interface{}, error) {
	apiURL := fmt.Sprintf("%s/%s", c.url, configPrefix)
	req, err := http.NewRequest(http.MethodGet, apiURL, nil)
	if err != nil {
		return nil, err
	}
	err = c.auth(req)
	if err != nil {
		return nil, err
	}
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	config := map[string]interface{}{}
	err = json.Unmarshal(body, &config)
	if err != nil {
		return nil, err
	}
	return config, nil
}

func (c *pdClient) PutConfig(conf map[string]interface{}) error {
	// special treatment for PD's update config API
	body := utils.FlattenMap(conf)

	apiURL := fmt.Sprintf("%s/%s", c.url, configPrefix)
	buf := new(bytes.Buffer)
	err := json.NewEncoder(buf).Encode(body)
	if err != nil {
		return errors.Wrap(err, "encoding PD put config request body")
	}
	req, err := http.NewRequest(http.MethodPost, apiURL, buf)
	if err != nil {
		return errors.Wrap(err, "PD put config build request")
	}
	if err := c.auth(req); err != nil {
		return err
	}
	res, err := c.client.Do(req)
	if err != nil {
		return errors.Wrap(err, "call PD put config API")
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return errors.Wrapf(err, "error calling PD put config, status code %d, cannot read error body", res.StatusCode)
		}
		return errors.Wrapf(err, "error calling PD put config, status code %d, error body %s", res.StatusCode, string(body))
	}
	return nil
}
