package rms

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/tidbcloud/pov-service/internal/model"
	"github.com/tidbcloud/pov-service/internal/pkg/utils"
	"golang.org/x/net/context"
)

type Client struct {
	portalBaseURL string
	token         string
}

// NewClient creates a new RMS client
// param portalBaseURL the base URL of the portal API
// param token the token for the portal API
// return a new RMS client
func NewClient(
	portalBaseURL string,
	token string,
) Client {
	return Client{
		portalBaseURL: portalBaseURL,
		token:         token,
	}
}

// ScaleTiDBGroupParams represents the parameters for scaling a TiDB group
type ScaleTiDBGroupParams struct {
	Replicas int `json:"replicas,omitempty"`
}

// TODO: @mahjonp add the cloud region on the cluster type
func getCloudRegion(cluster model.TiDBCluster) (string, error) {
	switch cluster.Type {
	case model.ClusterTypeTiDBCloudPremium:
		return "aws-us-west-2", nil
	case model.ClusterTypeTiDBCloudEssential:
		return "aws-us-east-1", nil
	}
	return "", fmt.Errorf("unknown cluster type: %s", cluster.Type)
}

// buildRMSURL builds the RMS URL for the given portal base URL and cloud region, it should be like "https://rms-internal.pingcap.net/rms/aws-us-east-1"
func buildRMSURL(portalBaseURL string, cloudRegion string) string {
	portalBaseURL = strings.TrimRight(portalBaseURL, "/")
	cloudRegion = strings.TrimRight(cloudRegion, "/")
	return fmt.Sprintf("%s/%s", portalBaseURL, cloudRegion)
}

// ScaleTiDBGroup scales a TiDB group in the specified testbed and cluster
func (c Client) ScaleTiDBGroup(
	ctx context.Context,
	cluster model.TiDBCluster,
	params ScaleTiDBGroupParams,
) error {
	body, err := json.Marshal(params)
	if err != nil {
		return err
	}
	cloudRegion, err := getCloudRegion(cluster)
	if err != nil {
		return err
	}
	var url string
	switch cluster.Type {
	case model.ClusterTypeTiDBCloudPremium:
		url = fmt.Sprintf("%s/api/v1/testbeds/%s/nextgen-cluster/tc/tidb-groups/keyspace1",
			buildRMSURL(c.portalBaseURL, cloudRegion), cluster.TestbedName)
	default:
		return fmt.Errorf("unknown cluster type: %s", cluster.Type)
	}
	req, err := http.NewRequest(
		"PATCH",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")

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

func (c Client) UpdateTiDBGroupRU(
	ctx context.Context,
	cluster model.TiDBCluster,
	minRU int64,
	maxRU int64,
) error {
	body, err := json.Marshal(map[string]any{
		"minRcu": fmt.Sprintf("%d", minRU),
		"maxRcu": fmt.Sprintf("%d", maxRU),
	})
	if err != nil {
		return err
	}
	cloudRegion, err := getCloudRegion(cluster)
	if err != nil {
		return err
	}
	var url string
	switch cluster.Type {
	case model.ClusterTypeTiDBCloudPremium:
		url = fmt.Sprintf("%s/api/v1/testbeds/%s/nextgen-cluster/tc/tidb-groups/keyspace1/modify",
			buildRMSURL(c.portalBaseURL, cloudRegion), cluster.TestbedName)
	case model.ClusterTypeTiDBCloudEssential:
		url = fmt.Sprintf("%s/api/v1/testbeds/%s/essential-cluster/tc/modify",
			buildRMSURL(c.portalBaseURL, cloudRegion), cluster.TestbedName)
	default:
		return fmt.Errorf("unknown cluster type: %s", cluster.Type)
	}
	req, err := http.NewRequest(
		"PATCH",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")

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

type InjectFailureFaultParams struct {
	Duration string `json:"duration"`
}

func (p *InjectFailureFaultParams) Validate() error {
	if p.Duration == "" {
		return fmt.Errorf("duration is required")
	}
	if _, err := time.ParseDuration(p.Duration); err != nil {
		return fmt.Errorf("invalid duration: %s", p.Duration)
	}
	return nil
}

type InjectTiDBGroupFailureParams struct {
	FailureFault *InjectFailureFaultParams `json:"failureFault,omitempty"`
}

func (p *InjectTiDBGroupFailureParams) Validate() error {
	// If no failure fault is provided, we don't need to inject any failure
	if p.FailureFault == nil {
		return fmt.Errorf("failureFault is required")
	}
	return p.FailureFault.Validate()
}

func (c Client) InjectTiDBGroupFailure(
	ctx context.Context,
	cluster model.TiDBCluster,
	params InjectTiDBGroupFailureParams,
) error {
	if err := params.Validate(); err != nil {
		return err
	}
	body, err := json.Marshal(params)
	if err != nil {
		return err
	}
	cloudRegion, err := getCloudRegion(cluster)
	if err != nil {
		return err
	}
	var url string
	switch cluster.Type {
	case model.ClusterTypeTiDBCloudPremium:
		url = fmt.Sprintf("%s/api/v1/testbeds/%s/nextgen-cluster/tc/tidb-groups/keyspace1/inject-fault",
			buildRMSURL(c.portalBaseURL, cloudRegion), cluster.TestbedName)
	default:
		return fmt.Errorf("unsupported cluster type: %s", cluster.Type)
	}
	req, err := http.NewRequest(
		"PATCH",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")

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

type RollbackTiDBGroupFailureParams struct{}

func (p *RollbackTiDBGroupFailureParams) Validate() error {
	return nil
}

func (c Client) RollbackTiDBGroupFailure(
	ctx context.Context,
	cluster model.TiDBCluster,
	params RollbackTiDBGroupFailureParams,
) error {
	if err := params.Validate(); err != nil {
		return err
	}
	body, err := json.Marshal(params)
	if err != nil {
		return err
	}
	cloudRegion, err := getCloudRegion(cluster)
	if err != nil {
		return err
	}
	var url string
	switch cluster.Type {
	case model.ClusterTypeTiDBCloudPremium:
		url = fmt.Sprintf("%s/api/v1/testbeds/%s/nextgen-cluster/tc/tidb-groups/keyspace1/rollback-fault",
			buildRMSURL(c.portalBaseURL, cloudRegion), cluster.TestbedName)
	default:
		return fmt.Errorf("unsupported cluster type: %s", cluster.Type)
	}
	req, err := http.NewRequest(
		"PATCH",
		url,
		bytes.NewBuffer(body))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")

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

func (c Client) send(req *http.Request) (*http.Response, error) {
	req.Header.Set("Authorization", "Bearer "+c.token)

	client := utils.NewDefaultHttpClient()
	return client.Send(req)
}
