package tiworkload

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"strconv"

	"github.com/PingCAP-QE/tiworkload/pkg/sign"
	"github.com/tidbcloud/pov-service/internal/model"
	"github.com/tidbcloud/pov-service/internal/pkg/utils"
)

const (
	MaxSampleDBs    = 10
	MaxSampleTables = 20
)

type Client struct {
	address string

	ak string
	sk string
}

func NewClient(
	address string,
	ak string,
	sk string) Client {
	return Client{
		address: address,
		ak:      ak,
		sk:      sk,
	}
}

func (c Client) Ping() error {
	url := c.address + "/ping"

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return err
	}

	req, err = sign.SignRequest(c.ak, c.sk, req)
	if err != nil {
		return err
	}

	client := &http.Client{}
	// Submit the request
	_, err = client.Do(req)
	return err
}

func (c Client) ListTraffics() ([]model.TrafficTmpl, error) {
	url := c.address + getPath(getWorkloads)

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.send(req)
	if err != nil {
		return nil, err
	}

	var ret []model.TrafficTmpl
	decoder := json.NewDecoder(resp.Body)
	return ret, decoder.Decode(&ret)
}

func (c Client) OneTrafficByName(name string) (model.TrafficTmpl, error) {
	url := c.address + getPath(getWorkload, name)

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return model.TrafficTmpl{}, err
	}

	resp, err := c.send(req)
	if err != nil {
		return model.TrafficTmpl{}, err
	}

	var ret model.TrafficTmpl
	decoder := json.NewDecoder(resp.Body)
	return ret, decoder.Decode(&ret)
}

// GetTrafficForm will only return workload forms of TiWorkload execForms(which include forms of varies tasks)
func (c Client) GetTrafficForm(name string) ([]model.WorkloadField, error) {
	url := c.address + getPath(getWorkloadForm, name)

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.send(req)
	if err != nil {
		return nil, err
	}

	var ret execForms
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return nil, err
	}
	return ret.Workload.ToWorkloadFields(), nil
}

func (c Client) RunTraffic(params WorkloadParams) (int64, error) {
	url := c.address + getPath(postWorkload)

	body, err := json.Marshal(params)
	if err != nil {
		return 0, err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return 0, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	resp, err := c.send(req)
	if err != nil {
		return 0, err
	}

	var ret struct {
		ID string `json:"id"`
	}
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return 0, err
	}
	tmp, err := strconv.Atoi(ret.ID)
	return int64(tmp), err
}

func (c Client) RecreateTraffic(
	ctx context.Context,
	oldId int64,
	newParams WorkloadParams) (int64, error) {
	url := c.address + getPath(postWorkloadRecreate)
	toMarshal := struct {
		OldId     string         `json:"old_workload_id"`
		NewParams WorkloadParams `json:"new_workload_params"`
	}{
		OldId:     strconv.Itoa(int(oldId)),
		NewParams: newParams,
	}

	body, err := json.Marshal(toMarshal)
	if err != nil {
		return 0, err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return 0, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	resp, err := c.send(req)
	if err != nil {
		return 0, err
	}

	var ret struct {
		ID string `json:"id"`
	}
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return 0, err
	}
	tmp, err := strconv.Atoi(ret.ID)
	return int64(tmp), err
}

func (c Client) GetTrafficExec(id int64) (TrafficExec, error) {
	url := c.address + getPath(getWorkloadExec, strconv.Itoa(int(id)))

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return TrafficExec{}, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.send(req)
	if err != nil {
		return TrafficExec{}, err
	}

	var ret TrafficExec
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return ret, err
	}
	return ret, nil
}

func (c Client) TerminateTrafficExec(id int64) error {
	url := c.address + getPath(deleteWorkloadExec, strconv.Itoa(int(id)))

	req, err := http.NewRequest(
		"DELETE",
		url,
		nil)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	resp, err := c.send(req)
	if err != nil {
		return err
	}

	var ret TrafficExec
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return err
	}
	return nil
}

func (c Client) SampleDBs(
	ctx context.Context,
	dbAccess DBAccess,
	opts model.SampleDBOptions, // TODO: page
) (*model.DBSampleResult, error) {
	url := c.address + getPath(postSampleDBs)

	param := struct {
		DBAccess
		model.SampleDBOptions
	}{
		DBAccess:        dbAccess,
		SampleDBOptions: opts,
	}

	body, err := json.Marshal(param)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.send(req)
	if err != nil {
		return nil, err
	}

	var ret model.DBSampleResult
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	return &ret, err
}

func (c Client) DefaultSampleDBs(
	ctx context.Context,
	ClusterID string,
	DBUser, DBPwd string,
) (*model.DBSampleResult, error) {
	return c.SampleDBs(ctx,
		DBAccess{
			ClusterID:  ClusterID,
			DBUser:     DBUser,
			DBPassword: DBPwd,
		},
		model.SampleDBOptions{},
	)
}

func (c Client) SampleData(
	ctx context.Context,
	dbAccess DBAccess,
	opts model.SampleDataOptions, // TODO: page
) (*model.TableQueryResult, error) {
	url := c.address + getPath(postSampleData)

	param := struct {
		DBAccess
		model.SampleDataOptions
	}{
		DBAccess:          dbAccess,
		SampleDataOptions: opts,
	}

	body, err := json.Marshal(param)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.send(req)
	if err != nil {
		return nil, err
	}

	var ret model.TableQueryResult
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)

	return &ret, err
}

func (c Client) PostOverProvision(
	ctx context.Context,
	clusterID string,
	option OverProvisionOption) (string, error) {
	url := c.address + getPath(postOverprovision, clusterID)

	body, err := json.Marshal(option)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	req = req.WithContext(ctx)

	_, err = c.send(req)
	if err != nil {
		return "", err
	}

	return option.Name, nil
}

func (c Client) DeleteOverprovision(
	ctx context.Context,
	clusterID, overprovName string) error {
	url := c.address + getPath(deleteOverprovision, clusterID, overprovName)

	req, err := http.NewRequest(
		"DELETE",
		url,
		nil)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	req = req.WithContext(ctx)

	_, err = c.send(req)
	return err
}

func (c Client) RegisterTestbed(
	ctx context.Context,
	name string) (string, error) {
	url := c.address + getPath(postCluster)

	params := struct {
		Position     string `json:"position"`
		CreatorEmail string `json:"creator_email"`
		DeployType   int    `json:"deploy_type,omitempty"`
		ConnType     int    `json:"conn_type,omitempty"`
	}{
		Position:     name,
		CreatorEmail: "tidbcloud-pov@pingcap.com",
		DeployType:   1,
		ConnType:     1,
	}

	body, err := json.Marshal(params)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	resp, err := c.send(req)
	if err != nil {
		return "", err
	}

	var ret struct {
		ID    string `json:"id"`
		Agent struct {
			ID string `json:"id"`
		} `json:"agent"`
	}
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return "", err
	}

	return ret.Agent.ID, err
}

func (c Client) Get1stClusterByAgent(
	ctx context.Context,
	agentId string) (string, error) {
	url := c.address + getPath(getCluster)

	params := struct {
		AgentID string `json:"agentId"`
		OwnerId int    `json:"ownerId"`
		// the backup field to be compatible with the old api
		BOwnerId int `json:"owner_id"`
	}{
		AgentID:  agentId,
		OwnerId:  99902,
		BOwnerId: 99902,
	}
	url = getUrl(url, params)

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	resp, err := c.send(req)
	if err != nil {
		return "", err
	}

	var ret struct {
		Total    int `json:"total"`
		Clusters []struct {
			ID     string          `json:"id"`
			Info   json.RawMessage `json:"info"`
			Handle string          `json:"handle"`
			Agent  struct {
				ID string `json:"id"`
			} `json:"agent"`
		} `json:"clusters"`
	}
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return "", err
	}
	if len(ret.Clusters) == 0 || ret.Clusters[0].Handle == "" {
		return "", nil
	}

	return ret.Clusters[0].ID, err
}
func (c Client) GetCluster(
	ctx context.Context,
	id string) (ClusterDetail, error) {
	url := c.address + getPath(getClusterDetail, id)

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return ClusterDetail{}, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")

	resp, err := c.send(req)
	if err != nil {
		return ClusterDetail{}, err
	}

	var ret ClusterDetail
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return ret, err
	}
	return ret, nil
}

func (c Client) GetPrepareForm(name string) ([]model.WorkloadField, error) {
	url := c.address + getPath(getWorkloadForm, name)

	req, err := http.NewRequest(
		"GET",
		url,
		nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.send(req)
	if err != nil {
		return nil, err
	}

	var ret execForms
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return nil, err
	}
	return ret.Prepare.ToWorkloadFields(), nil
}

func (c Client) RunPrepare(params PrepareParams) (int64, error) {
	url := c.address + getPath(postWorkload)

	body, err := json.Marshal(params)
	if err != nil {
		return 0, err
	}

	req, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return 0, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-Id", "99902")
	resp, err := c.send(req)
	if err != nil {
		return 0, err
	}

	var ret struct {
		ID string `json:"id"`
	}
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return 0, err
	}
	tmp, err := strconv.Atoi(ret.ID)
	return int64(tmp), err
}

func (c Client) ExecuteSql(
	ctx context.Context,
	req ExecteSqlReq,
) (ExecteSqlResp, error) {
	url := c.address + getPath(postExecuteSql)

	body, err := json.Marshal(req)
	if err != nil {
		return ExecteSqlResp{}, err
	}

	hreq, err := http.NewRequest(
		"POST",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return ExecteSqlResp{}, err
	}
	hreq.Header.Set("Content-Type", "application/json")
	hreq.Header.Set("X-User-Id", "99902")
	resp, err := c.send(hreq)
	if err != nil {
		return ExecteSqlResp{}, err
	}

	var ret ExecteSqlResp
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&ret)
	if err != nil {
		return ExecteSqlResp{}, err
	}
	return ret, err
}

func (c Client) send(req *http.Request) (*http.Response, error) {
	req, err := sign.SignRequest(c.ak, c.sk, req)
	if err != nil {
		return nil, err
	}
	client := utils.NewDefaultHttpClient()
	return client.Send(req)
}
