package regionalservercli

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"sigs.k8s.io/controller-runtime/pkg/client"

	v1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/message"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	_ "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
)

type MigrationServiceRSClient struct {
	endpoint   string
	httpClient *http.Client
}

func NewMigrationServiceRSClient(endpoint string) *MigrationServiceRSClient {
	return &MigrationServiceRSClient{
		endpoint:   endpoint,
		httpClient: http.DefaultClient,
	}
}

func (c *MigrationServiceRSClient) requestDoWithRetry(req *http.Request, retry int) ([]byte, error) {
	var lasterr error
	for i := retry; i >= 0; i-- {
		result, err := c.requestDo(req)
		if err == nil {
			return result, nil
		}
		if i > 0 {
			time.Sleep(500 * time.Millisecond)
		}
		lasterr = err
	}
	return nil, lasterr
}

func (c *MigrationServiceRSClient) requestDo(req *http.Request) ([]byte, error) {
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 300 {
		return nil, errors.New(string(data))
	}
	return data, nil
}

func (c *MigrationServiceRSClient) ImportAPPBox(ctx context.Context, cr *v1alpha1.APPBox) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/appbox", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) ImportAPPRoute(ctx context.Context, cr *v1alpha1.APPRoute) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/approute", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}
func (c *MigrationServiceRSClient) ImportInternet(ctx context.Context, cr *v1alpha1.Internet) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/internet", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) ImportPrivateLinkService(ctx context.Context, cr *v1alpha1.PrivateLinkService) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/privatelinkservice", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) ImportVPCPeering(ctx context.Context, cr *v1alpha1.VPCPeering) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/vpcpeering", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) ImportVPC(ctx context.Context, cr *v1alpha1.VPC) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/vpc", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) ImportO11yRoute(ctx context.Context, cr *v1alpha1.O11yRoute) error {
	bs, err := json.Marshal(cr)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/import/o11yroute", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) ImportO11yAgent(ctx context.Context, agent *message.O11yAgent) error {
	id := agent.ID
	bs, err := json.Marshal(agent)
	if err != nil {
		return err
	}
	u := fmt.Sprintf("%s/api/dedicated/v1/migration/o11yregional/ops/clusters/%s/import-agent", c.endpoint, id)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckAPPBox(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/appbox?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckAPPRoute(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/approute?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckInternet(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/internet?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckPrivateLinkService(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/privatelinkservice?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckVPCPeering(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/vpcpeering?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckVPC(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/vpc?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) CheckO11yRoute(ctx context.Context, key client.ObjectKey) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/check/o11yroute?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

// add retry logic to force success
func (c *MigrationServiceRSClient) ReconcileAPPBox(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/appbox?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcileAPPRoute(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/approute?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcileInternet(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/internet?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcilePrivateLinkService(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/privatelinkservice?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcileVPCPeering(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/vpcpeering?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcileVPC(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/vpc?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcileO11yRoute(ctx context.Context, key client.ObjectKey, pause bool) error {
	query := make(url.Values)
	if key.Namespace != "" {
		query.Add("namespace", key.Namespace)
	}
	query.Add("name", key.Name)
	query.Add("pause", strconv.FormatBool(pause))
	req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/reconcile/o11yroute?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) ReconcileO11yAgent(ctx context.Context, param *message.O11yAgentReconcileParam) error {
	query := make(url.Values)
	query.Add("pauseCluster", strconv.FormatBool(param.PauseCluster))
	query.Add("region", param.Region)
	query.Add("vendor", param.Vendor)
	u := fmt.Sprintf("%s/api/dedicated/v1/migration/o11yregional/ops/clusters/%s/pause-reconcile?%s", c.endpoint, param.ID, query.Encode())
	req, err := http.NewRequestWithContext(ctx, http.MethodPut, u, nil)
	if err != nil {
		return err
	}
	_, err = c.requestDoWithRetry(req, 2)
	return err
}

func (c *MigrationServiceRSClient) DiffEKSClusterSpec(ctx context.Context, clusterID string, spec *v1.EKSClusterSpec) error {
	query := make(url.Values)
	query.Add("clusterID", clusterID)
	bs, err := json.Marshal(spec)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/diff/eksclusterspec?%s", c.endpoint, query.Encode()), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}

func (c *MigrationServiceRSClient) DiffGKEClusterSpec(ctx context.Context, clusterID string, spec *v1.GKEClusterSpec) error {
	query := make(url.Values)
	query.Add("clusterID", clusterID)
	bs, err := json.Marshal(spec)
	if err != nil {
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/migration/dedicatedregional/diff/gkeclusterspec?%s", c.endpoint, query.Encode()), bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = c.requestDo(req)
	return err
}
