package regionserver

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/go-resty/resty/v2"
	clusterdedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/httputil"
)

const (
	apiPrefix                              = "/api/dedicated/v1"
	clusteragentApiPrefix                  = "/cluster-agent"
	vpcIdPathParameter                     = "vpcId"
	vpcPeeringIdPathParameter              = "vpcPeeringId"
	clusterIdParameter                     = "clusterId"
	projectIDParameter                     = "projectId"
	TenantIDParameter                      = "tenantId"
	tidbGroupIdParameter                   = "tidbGroupId"
	privateLinkServiceIdParameter          = "privateLinkServiceId"
	tidbGroupIdPathParameter               = "tidbGroupId"
	componentTypePathParameter             = "componentType"
	nodePoolNamePathParameter              = "nodePoolName"
	gkeHeterogeneousResourceNameParameter  = "name"
	showStatusOnlyParameter                = "showStatusOnly"
	statusesParameter                      = "statuses"
	privateLinkServiceMigrationIdParameter = "privateLinkServiceMigrationId"
)

var (
	operationMap = map[string]string{
		// Ping service
		"Ping": fmt.Sprintf("%s/ping", apiPrefix),

		// VPC service
		"CreateVpc":        fmt.Sprintf("%s/vpcs", apiPrefix),
		"ListVpcs":         fmt.Sprintf("%s/vpcs", apiPrefix),
		"GetVpc":           fmt.Sprintf("%s/vpcs/{%s}", apiPrefix, vpcIdPathParameter),
		"DeleteVpc":        fmt.Sprintf("%s/vpcs/{%s}", apiPrefix, vpcIdPathParameter),
		"AddSecondaryCidr": fmt.Sprintf("%s/vpcs/{%s}:addSecondaryCidr", apiPrefix, vpcIdPathParameter),

		// VPC peering service
		"CreateVpcPeering": fmt.Sprintf("%s/vpcPeerings", apiPrefix),
		"DeleteVpcPeering": fmt.Sprintf("%s/vpcPeerings/{%s}", apiPrefix, vpcPeeringIdPathParameter),
		"ListVpcPeerings":  fmt.Sprintf("%s/vpcPeerings", apiPrefix),
		"GetVpcPeering":    fmt.Sprintf("%s/vpcPeerings/{%s}", apiPrefix, vpcPeeringIdPathParameter),
		"AcceptVpcPeering": fmt.Sprintf("%s/vpcPeerings:accept", apiPrefix),

		// Cluster service
		"CreateCluster": fmt.Sprintf("%s/clusters", apiPrefix),
		"ListClusters":  fmt.Sprintf("%s/clusters", apiPrefix),
		"GetCluster":    fmt.Sprintf("%s/clusters/{%s}", apiPrefix, clusterIdParameter),
		"DeleteCluster": fmt.Sprintf("%s/clusters/{%s}", apiPrefix, clusterIdParameter),
		"ScaleCluster":  fmt.Sprintf("%s/clusters/{%s}:scale", apiPrefix, clusterIdParameter),
		"PauseCluster":  fmt.Sprintf("%s/clusters/{%s}:pause", apiPrefix, clusterIdParameter),
		"ResumeCluster": fmt.Sprintf("%s/clusters/{%s}:resume", apiPrefix, clusterIdParameter),
		"SetVersionAndConfigurations": fmt.Sprintf(
			"%s/clusters/{%s}:setVersionAndConfigurations",
			apiPrefix,
			clusterIdParameter,
		),
		"GetClusterProxyConfidentialInfoResponse": fmt.Sprintf(
			"%s/clusters/{%s}:getClusterProxyConfidentialInfo",
			apiPrefix,
			clusterIdParameter,
		),
		"GetComponentNodeInfos": fmt.Sprintf(
			"%s/clusters/{%s}:getComponentNodeInfos",
			apiPrefix,
			clusterIdParameter,
		),
		"GetCaCertificate": fmt.Sprintf(
			"%s/clusters/{%s}:getCaCertificate",
			apiPrefix,
			clusterIdParameter,
		),
		"SetClientCaCertificate": fmt.Sprintf(
			"%s/clusters/{%s}:setClientCaCertificate",
			apiPrefix,
			clusterIdParameter,
		),
		"GetClusterRole": fmt.Sprintf(
			"%s/clusters/{%s}:getClusterRole",
			apiPrefix,
			clusterIdParameter,
		),
		"ValidateSecurityConfiguration": fmt.Sprintf(
			"%s/clusterValidations:validateSecurityConfiguration",
			apiPrefix,
		),
		clusterdedicatedv1.ClusterService_CheckClusterPublicLBInboundRulesStatus_FullMethodName: fmt.Sprintf(
			"%s/clusters/{%s}:checkClusterPublicLBInboundRulesStatus",
			apiPrefix,
			clusterIdParameter,
		),
		"EnableGKEPolicyControl": fmt.Sprintf(
			"%s/clusters/{%s}/gkePolicyControl:enable",
			apiPrefix,
			clusterIdParameter,
		),
		"GetGKEPolicyControl": fmt.Sprintf(
			"%s/clusters/{%s}/gkePolicyControl",
			apiPrefix,
			clusterIdParameter,
		),
		"CreateGKEHeterogeneousDeploy": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/deploys",
			apiPrefix,
			clusterIdParameter,
		),
		"UpdateGKEHeterogeneousDeploy": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/deploys/{%s}",
			apiPrefix,
			clusterIdParameter,
			componentTypePathParameter,
		),
		"GetGKEHeterogeneousDeploy": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/deploys/{%s}",
			apiPrefix,
			clusterIdParameter,
			componentTypePathParameter,
		),
		"DeleteGKEHeterogeneousDeploy": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/deploys/{%s}",
			apiPrefix,
			clusterIdParameter,
			componentTypePathParameter,
		),
		"CreateGKEHeterogeneousNodePool": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/nodePools",
			apiPrefix,
			clusterIdParameter,
		),
		"GetGKEHeterogeneousNodePools": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/nodePools/{%s}",
			apiPrefix,
			clusterIdParameter,
			componentTypePathParameter,
		),
		"ListGKEHeterogeneousNodePools": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/nodePools",
			apiPrefix,
			clusterIdParameter,
		),
		"UpdateGKEHeterogeneousNodePool": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/nodePools/{%s}",
			apiPrefix,
			clusterIdParameter,
			nodePoolNamePathParameter,
		),
		"DeleteGKEHeterogeneousNodePool": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/nodePools/{%s}",
			apiPrefix,
			clusterIdParameter,
			nodePoolNamePathParameter,
		),
		"CreateGKEHeterogeneousComputeClass": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/computeClasses",
			apiPrefix,
			clusterIdParameter,
		),
		"GetGKEHeterogeneousComputeClass": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/computeClasses",
			apiPrefix,
			clusterIdParameter,
		),
		"DeleteGKEHeterogeneousComputeClass": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/computeClasses/{%s}",
			apiPrefix,
			clusterIdParameter,
			gkeHeterogeneousResourceNameParameter,
		),
		"CreateGKEHeterogeneousGatekeeperAssign": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/gatekeeperAssigns",
			apiPrefix,
			clusterIdParameter,
		),
		"GetGKEHeterogeneousGatekeeperAssign": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/gatekeeperAssigns/{%s}",
			apiPrefix,
			clusterIdParameter,
			componentTypePathParameter,
		),
		"DeleteGKEHeterogeneousGatekeeperAssign": fmt.Sprintf(
			"%s/clusters/{%s}/gkeHeterogeneous/gatekeeperAssigns/{%s}",
			apiPrefix,
			clusterIdParameter,
			gkeHeterogeneousResourceNameParameter,
		),

		// Private link connection service
		"CreatePrivateLinkService": fmt.Sprintf("%s/clusters/{%s}/privateLinkServices", apiPrefix, clusterIdParameter),
		"ListPrivateLinkServices":  fmt.Sprintf("%s/clusters/{%s}/privateLinkServices", apiPrefix, clusterIdParameter),
		"GetPrivateLinkService": fmt.Sprintf(
			"%s/clusters/{%s}/privateLinkServices/{%s}",
			apiPrefix,
			clusterIdParameter,
			privateLinkServiceIdParameter,
		),
		"UpdatePrivateLinkService": fmt.Sprintf(
			"%s/clusters/{%s}/privateLinkServices/{%s}",
			apiPrefix,
			clusterIdParameter,
			privateLinkServiceIdParameter,
		),
		"DeletePrivateLinkService": fmt.Sprintf(
			"%s/clusters/{%s}/privateLinkServices/{%s}",
			apiPrefix,
			clusterIdParameter,
			privateLinkServiceIdParameter,
		),
		"RejectVpcEndpointConnections": fmt.Sprintf(
			"%s/clusters/{%s}/privateLinkServices/{%s}:rejectVpcEndpointConnections",
			apiPrefix,
			clusterIdParameter,
			privateLinkServiceIdParameter,
		),
		"PausePrivateLinkService": fmt.Sprintf(
			"%s/clusters/{%s}/privateLinkServices/{%s}:pause",
			apiPrefix,
			clusterIdParameter,
			privateLinkServiceIdParameter,
		),
		"PurgePrivateLinkMetadata": fmt.Sprintf(
			"%s/clusters/{%s}/privateLinkServices/{%s}:purgeMetadata",
			apiPrefix,
			clusterIdParameter,
			privateLinkServiceIdParameter,
		),

		// Compatibility service. Old infra-api apis that are needed by dp team.
		"GetCompatNetwork": fmt.Sprintf("%s/compatnetworks/{%s}", apiPrefix, vpcIdPathParameter),
		"GetRegionConfig":  fmt.Sprintf("%s/regionconfig", apiPrefix),
		"ListAppBoxes":     fmt.Sprintf("%s/appboxes", apiPrefix),

		// TidbGroup service
		"CreateTidbGroup": fmt.Sprintf("%s/clusters/{%s}/tidbgroups", apiPrefix, clusterIdParameter),
		"GetTidbGroup": fmt.Sprintf(
			"%s/clusters/{%s}/tidbgroups/{%s}",
			apiPrefix,
			clusterIdParameter,
			tidbGroupIdPathParameter,
		),
		"ListTidbGroups": fmt.Sprintf("%s/clusters/{%s}/tidbgroups", apiPrefix, clusterIdParameter),
		"UpdateTidbGroup": fmt.Sprintf(
			"%s/clusters/{%s}/tidbgroups/{%s}",
			apiPrefix,
			clusterIdParameter,
			tidbGroupIdPathParameter,
		),
		"DeleteTidbGroup": fmt.Sprintf(
			"%s/clusters/{%s}/tidbgroups/{%s}",
			apiPrefix,
			clusterIdParameter,
			tidbGroupIdPathParameter,
		),
		"TidbGroupPrivateLinkIPAddressType": fmt.Sprintf(
			"%s/clusters/{%s}/tidbgroups/{%s}/privateLinkIpAddressType",
			apiPrefix,
			clusterIdParameter,
			tidbGroupIdPathParameter,
		),
		"ValidateTidbGroupPrivateLinkIPv4Only": fmt.Sprintf(
			"%s/clusters/{%s}/tidbgroups/{%s}/validatePrivateLinkIpv4Only",
			apiPrefix,
			clusterIdParameter,
			tidbGroupIdPathParameter,
		),
		// DB Audit Log Service
		"SetAuditLogConfiguration": fmt.Sprintf(
			"%s/clusters/{%s}:setAuditLogConfiguration",
			apiPrefix,
			clusterIdParameter,
		),
		"GetAuditLogConfiguration": fmt.Sprintf(
			"%s/clusters/{%s}:getAuditLogConfiguration",
			apiPrefix,
			clusterIdParameter,
		),
		"ValidateAuditLogConfiguration": fmt.Sprintf(
			"%s/clusters/{%s}:validateAuditLogConfiguration",
			apiPrefix,
			clusterIdParameter,
		),
		"ListAuditLogs": fmt.Sprintf("%s/clusters/{%s}:listAuditLogs", apiPrefix, clusterIdParameter),
		"GetAuditLogDownloadUrl": fmt.Sprintf(
			"%s/clusters/{%s}:getAuditLogDownloadURL",
			apiPrefix,
			clusterIdParameter,
		),

		// Account
		"AssignAccount": fmt.Sprintf("%s/accounts:assignAccount", apiPrefix),
		"GetAccount":    fmt.Sprintf("%s/accounts", apiPrefix),

		// PrivateLinkServiceMigration Service
		"CreatePrivateLinkServiceMigration": fmt.Sprintf("%s/privateLinkServiceMigrations", apiPrefix),
		"GetPrivateLinkServiceMigration":    fmt.Sprintf("%s/privateLinkServiceMigrations/{%s}", apiPrefix, privateLinkServiceMigrationIdParameter),
		"ListPrivateLinkServiceMigrations":  fmt.Sprintf("%s/privateLinkServiceMigrations", apiPrefix),

		// cluster agent service
		"ExecuteClusterAgentCommand": fmt.Sprintf(
			"%s/tidb{%s}/api/v1/clusters/{%s}:executeCommand",
			clusteragentApiPrefix,
			clusterIdParameter,
			clusterIdParameter,
		),

		"PauseClusterTc": fmt.Sprintf(
			"%s/tidb{%s}/api/v1/clusters/{%s}/tcs:batch_pause",
			clusteragentApiPrefix,
			clusterIdParameter,
			clusterIdParameter,
		),
		"DeleteClusterAstsAndResumeTc": fmt.Sprintf(
			"%s/tidb{%s}/api/v1/clusters/{%s}:delete_asts_and_resume_tc",
			clusteragentApiPrefix,
			clusterIdParameter,
			clusterIdParameter,
		),
		"ListClusterTcs": fmt.Sprintf(
			"%s/tidb{%s}/api/v1/clusters/{%s}/tcs",
			clusteragentApiPrefix,
			clusterIdParameter,
			clusterIdParameter,
		),
	}
)

type regionServerCliImpl struct {
	client *resty.Client
}

func NewRegionServerCli(cfg *config.Config) regionserver.Client {
	client := resty.New().
		SetTimeout(time.Duration(cfg.RegionServerConfig.RequestTimeoutInSec) * time.Second).
		SetRetryWaitTime(time.Duration(cfg.RegionServerConfig.RetryWaitTimeoutInSec) * time.Second).
		SetRetryCount(cfg.RegionServerConfig.RetryCount)
	svc := &regionServerCliImpl{
		client: client,
	}
	return svc
}

func (s *regionServerCliImpl) callRegion(
	ctx context.Context,
	region *entity.Region,
	method, apiPath string,
	setParam func(*resty.Request) *resty.Request,
) (*resty.Response, bizerr.BizError) {
	if region.State != globalcommonv1.Region_ACTIVE {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "region is not active")
	}

	resp, err := httputil.DoRequest(ctx, s.client, method, region.ServiceAddress, apiPath, func(req *resty.Request) {
		req.ForceContentType("application/json")
		setParam(req)
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "call region service failed: %s", err.Error())
	}

	if (resp.StatusCode() < http.StatusOK || resp.StatusCode() >= http.StatusMultipleChoices) &&
		resp.StatusCode() != http.StatusConflict { // 409 means the resource already exists, which is acceptable
		// Set biz code.
		bizCode, ok := bizerr.HttpStatus2BizErrCode[resp.StatusCode()]
		if !ok {
			bizCode = bizerr.ErrCodeRegionalAPI
		}
		// Parse error message from region server.
		errRes := &result{}
		if err := json.Unmarshal(resp.Body(), errRes); err != nil {
			return resp, bizerr.WrapErrorWithCode(err, bizCode,
				"regional server returned HTTP %d (%s)", resp.StatusCode(), resp.Request.URL)
		}
		return resp, bizerr.New(bizCode, "%s", errRes.Message).Chain("regional code %d", errRes.Code)
	}

	return resp, nil
}

// result is the response structure of the region server
type result struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data,omitempty"`
}
