package pd

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

	httputil "github.com/pingcap/test-infra/sdk/pkg/util/http"
	"github.com/pingcap/test-infra/sdk/resource"
	"github.com/tidwall/gjson"
)

// Client is a HTTP Client for PD.
type Client struct {
	*httputil.Client
	pdAddr string
}

// NewPDClient creates an HTTP Client for PD based on TiDBCluster.
func NewPDClient(tc resource.MySQLCompatible) (*Client, error) {
	pd, err := tc.ServiceURL(resource.PDAddr)
	if err != nil {
		return nil, err
	}
	return &Client{Client: httputil.NewHTTPClient(http.DefaultClient), pdAddr: pd.String()}, nil
}

// NewPDClient creates an HTTP Client for PD based on pd url.
func NewPDClientVarUrl(pd *url.URL) (*Client, error) {
	return &Client{Client: httputil.NewHTTPClient(http.DefaultClient), pdAddr: pd.String()}, nil
}

func (c *Client) Get(path string) (*http.Response, error) {
	if !strings.HasPrefix(path, "/") {
		return nil, errors.New("path should be prefixed with /")
	}
	u := strings.TrimSuffix(c.pdAddr, "/") + path
	req, err := http.NewRequest("GET", u, nil)
	if err != nil {
		return nil, err
	}
	return c.Client.Do(req)
}

func (c *Client) GetJsonBytes(path string) ([]byte, error) {
	resp, err := c.Get(path)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("GET request \"%s\", got %v %s", path, resp.StatusCode, string(b))
	}
	if !gjson.ValidBytes(b) {
		return nil, fmt.Errorf("get %s returned invalid JSON bytes.", resp.Request.URL.String())
	}
	return b, nil
}

func (c *Client) GetPDAddr() string {
	return c.pdAddr
}

func (c *Client) DeleteLabelRule(ruleID string) error {
	urlPrefix := "/pd/api/v1/config/region-label/rule"
	return c.Delete(c.GetPDAddr() + urlPrefix + "/" + url.QueryEscape(ruleID))
}

func (c *Client) SetConfig(name string, value interface{}) ([]byte, error) {
	urlPrefix := "/pd/api/v1/config"
	r := map[string]interface{}{name: value}
	data, _ := json.Marshal(r)
	return c.Post(c.GetPDAddr()+urlPrefix, "application/json", bytes.NewBuffer(data))
}
func (c *Client) SetAllStoreLimit(limit int) ([]byte, error) {
	storesPrefix := "/pd/api/v1/stores"
	r := map[string]interface{}{"rate": limit}
	data, _ := json.Marshal(r)
	return c.Post(c.GetPDAddr()+storesPrefix+"/limit", "application/json", bytes.NewBuffer(data))
}
func (c *Client) DeleteTombstone() error {
	storesPrefix := "/pd/api/v1/stores"
	return c.Delete(c.GetPDAddr() + storesPrefix + "/remove-tombstone")
}

func (c *Client) GetSchedulers() ([]byte, error) {
	urlPrefix := "/pd/api/v1/schedulers"
	return c.GetJsonBytes(urlPrefix)
}

func (c *Client) CreateScheduler(schedulerName string) ([]byte, error) {
	urlPrefix := "/pd/api/v1/schedulers"
	r := map[string]interface{}{"name": schedulerName}
	data, _ := json.Marshal(r)
	return c.Post(c.GetPDAddr()+urlPrefix, "application/json", bytes.NewBuffer(data))
}

func (c *Client) DeleteScheduler(schedulerName string) error {
	urlPrefix := "/pd/api/v1/schedulers"
	return c.Delete(c.GetPDAddr() + urlPrefix + "/" + schedulerName)
}

// PauseOrResumeScheduler pauses a scheduler for delay seconds or resume a paused scheduler.
// t == 0 : resume scheduler.
// t > 0 : scheduler delays t seconds.
func (c *Client) PauseOrResumeScheduler(schedulerName string, t int64) ([]byte, error) {
	urlPrefix := "/pd/api/v1/schedulers"
	r := map[string]interface{}{"delay": t}
	data, _ := json.Marshal(r)
	return c.Post(c.GetPDAddr()+urlPrefix+"/"+schedulerName, "application/json", bytes.NewBuffer(data))
}

// GetAllStoresInfo retrieve all stores info
func (c *Client) GetAllStoresInfo() ([]byte, error) {
	urlPrefix := "/pd/api/v1/stores"
	bytes, err := c.GetJsonBytes(urlPrefix)
	if err != nil {
		return nil, err
	}
	return bytes, nil
}
