package cluster

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/awss3"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/billing"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clusterconfig"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clustermetric"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clusterproxy"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/remoteencryptor"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	auditlogrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/auditlog"
	cidrrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cidr"
	clusterrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cmek"
	configbackuprepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/configbackup"
	endpointrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/endpoint"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/quota"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	spurepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/spu"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/spu"
	"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/featuregate"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

var (
	ErrMsgScaleInInsufficientStorage = "After performing this operation, all disks may exceed %.1f%% of the used space, which is not allowed."

	ErrMsgInvalidMinNodeQuantity = "node_quantity(%d) of the node_type(%s) is less than the minimum node quantity(%d)"
	ErrMsgInvalidNodeQuantity    = "node_quantity(%d) of the node_type(%s) is not allowed, it must be one of %v"

	ErrMsgInvalidNodeQuantityStep          = "node_quantity(%d) of the node_type(%s) must be a multiple of the step(%d)"
	ErrMsgInvalidNodeQuantityTableReplicas = "node_quantity(%d) of the node_type(%s) is less than the number of replicas for tables(%d)"
	ErrMsgInvalidClusterScaleDownStorage   = "node_type(%s) does not support decreasing storage size"
	ErrMsgInvalidClusterScaleIn            = "node_type(%s) does not support decreasing node quantity"

	ErrMsgInvalidClusterCombinationTiFlash = "if the vCPUs of TiDB or TiKV component is 2 or 4, then the cluster does not support TiFlash"
	ErrMsgInvalidClusterCombination        = "if the vCPUs of TiDB or TiKV component is 2 or 4, then their vCPUs need to be the same"

	ErrMsgClusterQuotaExceed                 = "the total number of %s nodes %d has exceeded the maximum allowed limit %d for your organization, please contact us to request to increase the quota, or see https://docs.pingcap.com/tidbcloud/beta/limitations-and-quotas for the limitations"
	ErrMsgInvalidMinStorageSize              = "storage_size_gib(%d) of the node_type(%s) is less than the minimum size(%d)"
	ErrMsgInvalidClusterStorageExceededLimit = "Unable to downgrade as the storage size of the current cluster has exceeded the maximum limit of the target specification you want to downgrade to."
	ErrMsgInvalidMaxStorageSize              = "storage_size_gib(%d) of the node_type(%s) is greater than the maximum size(%d)"

	tracer = otel.Tracer("domain.cluster")
)

type Service interface {
	Create(ctx context.Context, req *globaldedicatedv1.CreateClusterRequest) (*entity.Cluster, bizerr.BizError)
	Scale(
		ctx context.Context,
		req *globaldedicatedv1.ScaleClusterWithMultiComponentRequest,
	) (*entity.Cluster, bizerr.BizError)
	List(ctx context.Context, req *globaldedicatedv1.ListClustersRequest) ([]*entity.Cluster, bizerr.BizError)
	Get(ctx context.Context, req *globaldedicatedv1.GetClusterRequest) (*entity.Cluster, bizerr.BizError)
	Pause(ctx context.Context, req *globaldedicatedv1.PauseClusterRequest) (*entity.Cluster, bizerr.BizError)
	Resume(
		ctx context.Context,
		req *globaldedicatedv1.ResumeClusterRequest,
		scheduledTime *time.Time,
	) (*entity.Cluster, bizerr.BizError)
	Delete(ctx context.Context, req *globaldedicatedv1.DeleteClusterRequest) (*entity.Cluster, bizerr.BizError)
	GetComponentNodeInfo(
		ctx context.Context,
		req *globaldedicatedv1.GetComponentNodeInfoRequest,
	) (*globaldedicatedv1.ComponentNodeInfo, bizerr.BizError)
	ApplyGKEHeterogeneousDeploy(
		ctx context.Context,
		req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
	) (*globaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError)
	GetGKEHeterogeneousDeploy(
		ctx context.Context,
		req *globaldedicatedv1.GetGKEHeterogeneousDeployRequest,
	) (*globaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError)
	DeleteGKEHeterogeneousDeploy(
		ctx context.Context,
		req *globaldedicatedv1.DeleteGKEHeterogeneousDeployRequest,
	) bizerr.BizError
	SetClientCaCertificate(ctx context.Context, req *globaldedicatedv1.SetClientCaCertificateRequest) bizerr.BizError
	GetClientCaCertificate(
		ctx context.Context,
		req *globaldedicatedv1.GetClientCaCertificateRequest,
	) (*globaldedicatedv1.GetClientCaCertificateResponse, bizerr.BizError)
	GetClusterCACertificate(ctx context.Context, clusterId uint64) ([]byte, bizerr.BizError)
	GetClusterCACertificateDownloadURL(ctx context.Context, clusterId uint64) (string, bizerr.BizError)
	ChangeRootPasswd(ctx context.Context, clusterId uint64, passwd string) bizerr.BizError
	ApplyCluster(ctx context.Context, req *globaldedicatedv1.ApplyClusterRequest) (*entity.Cluster, bizerr.BizError)
	GetBillableClustersCount(
		ctx context.Context,
		request *globaldedicatedv1.GetBillableClustersCountRequest,
	) (*globaldedicatedv1.GetBillableClustersCountResponse, bizerr.BizError)
	GetClusterConfidentialInfo(
		ctx context.Context,
		request *globaldedicatedv1.GetClusterConfidentialInfoRequest,
	) (*globaldedicatedv1.GetClusterConfidentialInfoResponse, bizerr.BizError)
	GetDefaultTidbVersion(
		ctx context.Context,
		request *globaldedicatedv1.GetDefaultTidbVersionRequest,
	) (*globaldedicatedv1.GetDefaultTidbVersionResponse, bizerr.BizError)
	GetLogRedactionPolicy(
		ctx context.Context,
		clusterId uint64,
	) (*globaldedicatedv1.LogRedactionPolicy, bizerr.BizError)
	UpdateLogRedactionPolicy(
		ctx context.Context,
		req *globaldedicatedv1.UpdateLogRedactionPolicyRequest,
	) bizerr.BizError
	BuildDesiredComponents(
		ctx context.Context,
		request *globaldedicatedv1.BuildDesiredComponentsRequest,
	) (*globaldedicatedv1.BuildDesiredComponentsResponse, bizerr.BizError)
	SyncSystemVariables(
		ctx context.Context,
		req *globaldedicatedv1.SyncSystemVariablesRequest,
	) bizerr.BizError
	GetClusterCustomParams(
		ctx context.Context,
		request *globaldedicatedv1.GetClusterCustomParamsRequest,
	) (*globaldedicatedv1.GetClusterCustomParamsResponse, bizerr.BizError)
	SyncPdOnlineConfig(
		ctx context.Context,
		req *globaldedicatedv1.SyncPdOnlineConfigRequest,
	) bizerr.BizError
	ShutdownUpgradeCluster(
		ctx context.Context,
		req *globaldedicatedv1.ShutdownUpgradeClusterRequest,
	) bizerr.BizError
	EnableClusterConfigBackup(
		ctx context.Context,
		req *globaldedicatedv1.EnableClusterConfigBackupRequest,
	) (*globaldedicatedv1.ClusterConfigBackupPolicy, bizerr.BizError)
	DisableClusterConfigBackup(
		ctx context.Context,
		req *globaldedicatedv1.DisableClusterConfigBackupRequest,
	) (*globaldedicatedv1.ClusterConfigBackupPolicy, bizerr.BizError)
	ListClusterConfigBackup(
		ctx context.Context,
		req *globaldedicatedv1.ListClusterConfigBackupRequest,
	) (*globaldedicatedv1.ListClusterConfigBackupResponse, bizerr.BizError)
	RunDueClusterConfigBackups(ctx context.Context, limit int) error
}

type serviceImpl struct {
	workflow workflow.Workflow

	featGateCli featuregate.Service
	skuSvc      spu.SkuService

	transaction             db.Transactioner
	clusterRepo             clusterrepo.Repo
	regionRepo              region.Repo
	cmekRepo                cmek.Repo
	operationRepo           clusterrepo.OperationRepo
	tidbGroupRepo           tidbgroup.Repo
	tenantClusterQuotaRepo  quota.TenantClusterQuotaRepo
	componentRepo           clusterrepo.ComponentRepo
	trafficFilterRepo       endpointrepo.TrafficFilterRepo
	privateLinkEndpointRepo endpointrepo.PrivateLinkEndpointRepo
	componentSnapshotRepo   clusterrepo.ComponentSnapshotRepo
	cidrRepo                cidrrepo.Repo
	instanceMuRepo          spurepo.InstanceMuRepo
	pausePlanRepo           clusterrepo.PausePlanRepo
	auditLogConfigRepo      auditlogrepo.ConfigRepo
	auditLogFilterRuleRepo  auditlogrepo.FilterRuleRepo
	configBackupPolicyRepo  configbackuprepo.PolicyRepo
	configBackupRepo        configbackuprepo.BackupRepo

	accountCli           account.Client
	metricsCli           clustermetric.Client
	billingCli           billing.Client
	clusterProxyProvider clusterproxy.Provider
	regionServerCli      regionserver.Client
	clusterConfigCli     clusterconfig.Client
	idGeneratorAdapter   idgenerator.IDGenerator
	encryptor            remoteencryptor.Client

	eventCommitter eventbus.WALCommitter

	s3GetClient     awss3.GetClient
	s3PresignClient awss3.PresignClient

	cfg *config.Config
}

func NewService(
	cfg *config.Config,
	flow workflow.Workflow,
	clusterRepo clusterrepo.Repo,
	componentRepo clusterrepo.ComponentRepo,
	transaction db.Transactioner,
	skuSvc spu.SkuService,
	regionServerCli regionserver.Client,
	cmekRepo cmek.Repo,
	featGate featuregate.Service,
	regionRepo region.Repo,
	opRepo clusterrepo.OperationRepo,
	tidbGroupRepo tidbgroup.Repo,
	tenantClusterQuotaRepo quota.TenantClusterQuotaRepo,
	vpcPeeringRepo endpointrepo.VpcPeeringRepo,
	pausePlanRepo clusterrepo.PausePlanRepo,
	auditLogConfigRepo auditlogrepo.ConfigRepo,
	auditLogFilterRuleRepo auditlogrepo.FilterRuleRepo,
	metricCli clustermetric.Client,
	accountCli account.Client,
	billingCli billing.Client,
	clusterProxyProvider clusterproxy.Provider,
	clusterConfigCli clusterconfig.Client,
	idGeneratorAdapter idgenerator.IDGenerator,
	trafficFilterRepo endpointrepo.TrafficFilterRepo,
	privateLinkEndpointRepo endpointrepo.PrivateLinkEndpointRepo,
	componentSnapshotRepo clusterrepo.ComponentSnapshotRepo,
	encryptor remoteencryptor.Client,
	cidrRepo cidrrepo.Repo,
	committer eventbus.WALCommitter,
	instanceMuRepo spurepo.InstanceMuRepo,
	s3GetClient awss3.GetClient,
	s3PresignClient awss3.PresignClient,
	configBackupPolicyRepo configbackuprepo.PolicyRepo,
	configBackupRepo configbackuprepo.BackupRepo,
) (Service, error) {
	return &serviceImpl{
		workflow:                flow,
		featGateCli:             featGate,
		clusterRepo:             clusterRepo,
		regionRepo:              regionRepo,
		cmekRepo:                cmekRepo,
		operationRepo:           opRepo,
		tidbGroupRepo:           tidbGroupRepo,
		tenantClusterQuotaRepo:  tenantClusterQuotaRepo,
		componentRepo:           componentRepo,
		transaction:             transaction,
		skuSvc:                  skuSvc,
		accountCli:              accountCli,
		metricsCli:              metricCli,
		billingCli:              billingCli,
		clusterProxyProvider:    clusterProxyProvider,
		regionServerCli:         regionServerCli,
		clusterConfigCli:        clusterConfigCli,
		idGeneratorAdapter:      idGeneratorAdapter,
		trafficFilterRepo:       trafficFilterRepo,
		privateLinkEndpointRepo: privateLinkEndpointRepo,
		componentSnapshotRepo:   componentSnapshotRepo,
		encryptor:               encryptor,
		cidrRepo:                cidrRepo,
		eventCommitter:          committer,
		instanceMuRepo:          instanceMuRepo,
		pausePlanRepo:           pausePlanRepo,
		auditLogConfigRepo:      auditLogConfigRepo,
		auditLogFilterRuleRepo:  auditLogFilterRuleRepo,
		cfg:                     cfg,
		s3GetClient:             s3GetClient,
		s3PresignClient:         s3PresignClient,
		configBackupPolicyRepo:  configBackupPolicyRepo,
		configBackupRepo:        configBackupRepo,
	}, nil
}

func (s *serviceImpl) GetClusterConfidentialInfo(
	ctx context.Context,
	request *globaldedicatedv1.GetClusterConfidentialInfoRequest,
) (*globaldedicatedv1.GetClusterConfidentialInfoResponse, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.GetClusterConfidentialInfo")
	defer span.End()

	cluster, err := s.clusterRepo.Get(ctx, request.ClusterId, true)
	if err != nil {
		return nil, err
	}
	out := &globaldedicatedv1.GetClusterConfidentialInfoResponse{
		EncryptedCloudAdminPassword: cluster.EncryptedCloudAdminPassword,
	}
	return out, nil
}

func (s *serviceImpl) GetBillableClustersCount(
	ctx context.Context,
	request *globaldedicatedv1.GetBillableClustersCountRequest,
) (*globaldedicatedv1.GetBillableClustersCountResponse, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.GetBillableClustersCount")
	defer span.End()

	count, err := s.clusterRepo.GetBillableClustersCount(
		ctx,
		request.ProjectId,
		request.StartTime.AsTime(),
		request.EndTime.AsTime(),
	)
	if err != nil {
		return nil, err
	}
	out := &globaldedicatedv1.GetBillableClustersCountResponse{
		BillableClustersCount: count,
	}
	return out, nil
}
