package sdk

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/pingcap/errors"

	"github.com/tidbcloud/infra-api/gitops-sdk/utils"
)

const configPrefix = "pd/api/v1/config"

type PDAuth func(*http.Request) error

type Option func(p *pdClient)

func Auth(auth PDAuth) Option {
	return func(p *pdClient) {
		p.auth = auth
	}
}

func HttpClient(client *http.Client) Option {
	return func(p *pdClient) {
		p.client = client
	}
}

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

func NewDefaultPDClient(url string, opts ...Option) DynamicPDClient {
	p := &pdClient{
		url: url,
		client: &http.Client{
			Timeout: 10 * time.Second,
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					// nolint: gosec
					InsecureSkipVerify: true,
				},
			},
		},
	}
	for _, opt := range opts {
		opt(p)
	}
	return p
}

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
	}
	if c.auth != nil {
		if err = c.auth(req); 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.Annotate(err, "encoding PD put config request body")
	}
	req, err := http.NewRequest(http.MethodPost, apiURL, buf)
	if err != nil {
		return errors.Annotate(err, "PD put config build request")
	}
	if c.auth != nil {
		if err = c.auth(req); err != nil {
			return err
		}
	}
	res, err := c.client.Do(req)
	if err != nil {
		return errors.Annotate(err, "call PD put config API")
	}
	defer res.Body.Close()
	if res.StatusCode > 299 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return errors.Annotatef(err, "error calling PD put config, status code %d, cannot read error body", res.StatusCode)
		}
		return errors.Annotatef(err, "error calling PD put config, status code %d, error body %s", res.StatusCode, string(body))
	}
	return nil
}
