package infraapi

import (
	"context"
	"strconv"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/dataflow-service/internal/config"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"go.opentelemetry.io/otel"
)

var Tracer = otel.Tracer("dfs/infra-api")

// TagPrefix is the prefix of label that  will be propagated to the underlay resource.
// etc: tags.tidbcloud.com/tenant then the tc created by infra-api will have the "tenant" label.
const (
	TagPrefix      = "tags.tidbcloud.com/"
	TagTenant      = "tenant"
	TagProject     = "project"
	TagCluster     = "cluster"
	TagComponent   = "component"
	TagEnvironment = "environment"
	TagServiceType = "servicetype"
	TagUsedBy      = "usedby"

	TagBackupMode = "backupmode"
)

const (
	TenantLabel      = TagPrefix + TagTenant
	ProjectLabel     = TagPrefix + TagProject
	ClusterLabel     = TagPrefix + TagCluster
	ComponentLabel   = TagPrefix + TagComponent
	EnvironmentLabel = TagPrefix + TagEnvironment
	ServiceTypeLabel = TagPrefix + TagServiceType
	UsedByLabel      = TagPrefix + TagUsedBy

	UsedByCustom         = "custom-tidb"
	ServiceTypeDedicated = "dedicated"

	backupModeLabel = TagPrefix + TagBackupMode
	managedByLabel  = "tidbcloud.com/managed-by"
)

// types for provider type
const (
	ProviderTypeAzure      string = "azure"
	ProviderTypeAWS        string = "aws"
	ProviderTypeEKS        string = "eks"
	ProviderTypeGCP        string = "gcp"
	ProviderTypeGKE        string = "gke"
	ProviderTypeAKS        string = "aks"
	ProviderTypeAWSDevTier string = "aws-free-tier"
)

type LightningConfigParam struct {
	TiDBPort     int32
	TiDBHost     string
	TiDBUserName string
	PDAddr       string
	Backend      string
}

type LightningGetter interface {
	// TCName get the tidbcluster name create by the specified cluster.
	TCName(clusterName string) string
	// GetLightningConfigParam get the param for newLightningConfig.
	GetLightningConfigParam() *LightningConfigParam
}

type BackupFacade interface {
	CreateBackupIgnoreExists(ctx context.Context, cloudProvider, region string, param *CreateBackupParam) error
	GetBackup(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, backupCRName string) (*cloudv1.Backup, error)
	ListBackups(ctx context.Context, region string, projectID, clusterID uint64) ([]cloudv1.Backup, error)
	ListLogBackups(ctx context.Context) ([]cloudv1.Backup, error)
	StartLogBackupIgnoreExists(ctx context.Context, cloudProvider, region string, param *BackupParam) error
	StopLogBackup(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, logBackupName string) error
	TruncateLogBackup(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, logBackupName, truncateUntil string) error
	DeleteLogBackupIgnoreNotFound(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, logBackupName string) error
	DeleteBackupIgnoreNotFound(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, backupName string) error
	// RawCreateBackupCR creates a Backup CR by passing through the raw Backup CR object
	// (for example, one constructed from a YAML payload) without translating its spec.
	// Used during log backup migration to pass through the full CR spec without translation.
	RawCreateBackupCR(ctx context.Context, cloudProvider, region string, tenantID, projectID, clusterID uint64, backup *cloudv1.Backup) error
	// RawGetBackupCR retrieves a Backup CR and returns the raw Backup CR object
	// (for example, as defined by a YAML payload) without translating its spec.
	// Used during log backup migration to compare CR status between global and regional.
	RawGetBackupCR(ctx context.Context, cloudProvider, region string, tenantID, projectID, clusterID uint64, backupName string) (*cloudv1.Backup, error)
}

type RestoreFacade interface {
	GetRestore(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, restoreName string) (*cloudv1.Restore, error)
	CreateRestoreIgnoreExists(ctx context.Context, cloudProvider, region string, param *RestoreParam) error
	DeleteRestoreIgnoreNotFound(ctx context.Context, cloudProvider, region string, tenantID uint64, projectID uint64, clusterID uint64, restoreName string) error
}

type TiCDCFacade interface {
	CreateTicdcIgnoreExist(ctx context.Context, projectID uint64, clusterID uint64, cr *cloudv1.TiCDC) error
	UpdateTicdc(ctx context.Context, d *cloudv1.TiCDC) error
	DeleteTicdcIfExist(ctx context.Context, projectID uint64, clusterID uint64) error
	GetTicdc(ctx context.Context, projectID, clusterID uint64) (*cloudv1.TiCDC, error)
	CanScaleTiCDC(cdcCluster *cloudv1.TiCDC) bool
	IsTiCDCEnabledScaling(cdcCluster *cloudv1.TiCDC) bool
}

type CMFacade interface {
	CreateNamespaceIfNotExist(ctx context.Context, projectID uint64) error
	// deprecated
	GetServerlessCluster(ctx context.Context, projectID uint64, clusterID uint64) (c *cloudv1.Cluster, err error)
	// deprecated
	UpdateServerlessCluster(ctx context.Context, c *cloudv1.Cluster) error
	// deprecated
	GetNetwork(ctx context.Context, providerType string, projectID uint64, region string) (network *cloudv1.Network, err error)
	GetCluster(ctx context.Context, projectID uint64, clusterID uint64) (c *cloudv1.Cluster, err error)
	ListBaseClusterConfig(ctx context.Context) ([]cloudv1.ClusterConfig, error)
}

type ListClusterConfigFilter struct {
	TenantID  *uint64
	ClusterID *uint64
	ManagedBy *string
}

func GetClusterVersion(cluster *cloudv1.Cluster) (string, error) {
	if cluster.Status.DB == nil || cluster.Status.DB.Version == "" {
		return "", errors.Errorf("Cluster %s does not have a DB Status", cluster.Name)
	}
	return cluster.Status.DB.Version, nil
}

type ClusterStatus struct {
	cloudv1.GardenerStatus
	*cloudv1.KubernetesStatus
}

func ClusterName(id uint64) string {
	return strconv.FormatUint(id, 10)
}

func NetworkName(providerType string, region string) string {
	return providerType + "-" + region
}

// Namespace return the namespace of infra-api
// projectName is any name of one instance of dbaas deployment can can not be changed.
func Namespace(projectName string, projectID uint64) string {
	return projectName + "-" + strconv.FormatUint(projectID, 10)
}

func GetObjectNamespace(projectID uint64) string {
	return Namespace(config.Global().GardenerConfig.GardenerProjectName, projectID)
}
