package config

import "github.com/tidbcloud/serverless-service/internal/domain/constant"

var globalConf = &CentralConfig{}

type CentralConfig struct {
	ServerConfig   ServerConfig                             `mapstructure:"server"`
	WorkerConfig   WorkerConfig                             `mapstructure:"worker"`
	SvcAddrConfig  SvcAddrConfig                            `mapstructure:"svcaddr"`
	GardenerConfig GardenerConfig                           `mapstructure:"gardener"`
	InfraAPIConfig InfraAPIConfig                           `mapstructure:"infraAPI"`
	Auth0Config    Auth0Config                              `mapstructure:"auth0"`
	MysqlConfig    MysqlConfig                              `mapstructure:"mysql"`
	CDCConfig      CDCConfig                                `mapstructure:"cdc"`
	BizConfig      BizConfig                                `mapstructure:"biz"`
	FeatureGates   map[constant.FeatureGateName]FeatureGate `mapstructure:"featureGates"`
	SendGridConfig SendGridConfig                           `mapstructure:"sendGrid"`
	OssConfig      OssConfig                                `mapstructure:"oss"`
	DMConfig       DMConfig                                 `mapstructure:"dm"`
}

type CDCConfig struct {
	NewArch CDCNewArchConfig `mapstructure:"newArch"`
}

type CDCNewArchConfig struct {
	Enabled bool `mapstructure:"enabled"              default:"false"`
	// EnabledClusters is an optional cluster-level allowlist. A cluster is
	// allowed to use newarch when its ID is listed here or its tenant is
	// enabled by the cdc_newarch feature gate.
	EnabledClusters     []string              `mapstructure:"enabledClusters"`
	Namespace           string                `mapstructure:"namespace"            default:"tidb-serverless"`
	UpstreamNamespace   string                `mapstructure:"upstreamNamespace"    default:"tidb-serverless"`
	UpstreamClusterName string                `mapstructure:"upstreamClusterName"  default:"serverless-cluster"`
	ShareScope          string                `mapstructure:"shareScope"           default:"global"`
	ShareRules          []CDCNewArchShareRule `mapstructure:"shareRules"`
	// BaseImage is the global fallback base image for newarch TiCDC.
	// Region-specific canary / rollout config lives in RegionConfig.
	BaseImage string `mapstructure:"baseImage"            default:""`
	// BaseImagePerProvider holds one base image per cloud provider
	// (aws / gcp / azure / alicloud). Each provider's registry layout
	// differs (e.g. ECR vs ACR vs GCR), so a single string can't cover
	// all of them. Keyed by the same provider names used elsewhere in
	// config (case sensitive; lowercased at lookup time).
	BaseImagePerProvider map[string]string `mapstructure:"baseImagePerProvider"`
	Version              string            `mapstructure:"version"              default:""`
	// RegionConfig overrides the global image config for a specific
	// provider + region. This is the preferred place for canarying a
	// different TiCDC registry or version in one region without touching
	// worker.regionConfig or triggering global rollout.
	RegionConfig []CDCNewArchRegionalConfig `mapstructure:"regionConfig"`

	// IssuerKind / IssuerName point at the cert-manager Issuer used to
	// stamp the per-cluster client and server certs that tidb-operator v1
	// expects (<cr-name>-cluster-client-secret and <cr-name>-ticdc-cluster-secret).

	IssuerKind string `mapstructure:"issuerKind" default:"ClusterIssuer"`
	IssuerName string `mapstructure:"issuerName" default:"tidb-serverless-issuer"`

	// RcuTiers is the user-facing rcu ladder exposed by the public
	// ListChangefeedSpecifications RPC. UI picks an rcu tier (and the
	// caller sees the rpsLimit on it). Internal compute / spec drift
	// checks do NOT use RcuTiers — see InstanceTypes + CpuPerRcu.
	//
	// Must be sorted by RCU ascending. If empty, falls back to
	// DefaultNewArchRcuTiers.
	RcuTiers []CDCNewArchRcuTier `mapstructure:"rcuTiers"`

	// CpuPerRcu is the conversion factor used to map a changefeed's
	// rcu request into total cpu demand on the shared TiCDC cluster.
	// totalCpu = totalRcu * cpuPerRcu. Default 1.
	CpuPerRcu uint32 `mapstructure:"cpuPerRcu" default:"1"`

	// InstanceTypes is the catalog of cdc machine shapes the picker
	// is allowed to choose from. Must be sorted ascending by
	// CpuPerReplica. The picker mirrors the previous
	// specification-ladder semantics: smallest single-replica fit;
	// when totalCpu exceeds the largest catalog entry, the largest
	// type is scaled by replica count.
	//
	// If empty, falls back to DefaultNewArchInstanceTypes (a single
	// 16c machine — so even a 2rcu changefeed materializes a 16c
	// shared cluster by design).
	InstanceTypes []CDCNewArchInstanceType `mapstructure:"instanceTypes"`

	// NodeSelector is copied into TiCDCSpec.ComponentSpec.NodeSelector
	// on the generated TidbCluster CR. Each dataplane has its own
	// nodepool labelling convention — eg. serverless dev uses
	// serverless.tidbcloud.com/node=<role>; the cdc spec needs to land
	// on a pool that fits its cpu/memory. Leaving empty falls back to
	// the cluster's default scheduling, which on tainted nodepools will
	// keep the pod Pending — that's a deployment bug worth catching
	// early rather than silently misplacing the pod.
	NodeSelector map[string]string `mapstructure:"nodeSelector"`

	// Tolerations is copied into TiCDCSpec.ComponentSpec.Tolerations on
	// the generated TidbCluster CR. Set this whenever the target
	// nodepool carries a taint (eg. serverless.tidbcloud.com/node=cdc).
	// Without a matching toleration the cdc pod stays Pending.
	Tolerations []NewArchToleration `mapstructure:"tolerations"`
}

type CDCNewArchRegionalConfig struct {
	Provider  string `mapstructure:"provider"`
	Region    string `mapstructure:"region"`
	BaseImage string `mapstructure:"baseImage"`
	Version   string `mapstructure:"version"`
}

// CDCNewArchShareRule lets operators choose TiCDC sharing granularity
// using IDs known before/while creating a changefeed. Rules can match
// tenant_id and/or cluster_id. If multiple rules match, resolution uses
// a fixed priority: changefeed > cluster > tenant > shareScope default.
type CDCNewArchShareRule struct {
	ShareScope string   `mapstructure:"shareScope"`
	TenantIDs  []string `mapstructure:"tenantIDs"`
	ClusterIDs []string `mapstructure:"clusterIDs"`
}

// NewArchToleration mirrors the subset of corev1.Toleration that
// operators can configure from yaml. It's a plain
// mapstructure-friendly struct so we don't drag the whole k8s API
// schema into the config layer; cdc_newarch.go converts it into a
// corev1.Toleration when building the CR.
type NewArchToleration struct {
	Key      string `mapstructure:"key"`
	Operator string `mapstructure:"operator" default:"Equal"`
	Value    string `mapstructure:"value"`
	Effect   string `mapstructure:"effect"   default:"NoSchedule"`
}

// CDCNewArchRcuTier is one entry of the user-facing rcu ladder. It
// only carries fields callers care about (rcu + rpsLimit); the
// machine-side dimensions (cpu / memory / storage / iops) live in
// CDCNewArchInstanceType. The two are deliberately decoupled so
// ops can change the catalog of cdc machines without touching the
// rcu tiers UI surfaces.
type CDCNewArchRcuTier struct {
	// RCU is the requested capacity unit. Stable id for the tier;
	// referenced in DB rows as "<rcu>rcu".
	RCU uint32 `mapstructure:"rcu"`
	// RPSLimit is published for the caller's reference; serverless-service
	// itself does not enforce it (the cdc kernel does).
	RPSLimit uint32 `mapstructure:"rpsLimit"`
}

// CDCNewArchInstanceType is one machine shape the cdc picker can
// choose from. cpu_per_replica is the dimension the picker compares
// against the shard's aggregated cpu demand
// (totalRcu * CpuPerRcu). Memory / storage / iops are pinned per
// replica.
type CDCNewArchInstanceType struct {
	// Name is the catalog id (e.g. "16c", "small"). Carried into the
	// generated TidbCluster CR's label set for observability.
	Name string `mapstructure:"name"`
	// CpuPerReplica is the cpu request and limit on each TiCDC pod.
	// The picker uses this as the per-replica capacity ceiling.
	CpuPerReplica uint32 `mapstructure:"cpuPerReplica"`
	// MemoryGi is the memory request and limit on each TiCDC pod.
	MemoryGi uint32 `mapstructure:"memoryGi"`
	// StorageGi is the size of the per-replica PVC mounted at
	// /var/lib/ticdc. Each replica gets its own PVC of this size.
	StorageGi uint32 `mapstructure:"storageGi"`
	// Iops / ThroughputMbs feed ensureNewArchStorageClass and drive
	// the per-cloud SC parameters (cdc-{cloud}-{iops}-{throughput}).
	Iops          uint32 `mapstructure:"iops"`
	ThroughputMbs uint32 `mapstructure:"throughputMbs"`
}

type DMConfig struct {
	ImportThreads int32 `mapstructure:"importThreads" default:"16"`
	ExportThreads int32 `mapstructure:"exportThreads" default:"4"`
	ReplThreads   int32 `mapstructure:"replThreads"   default:"16"`
	ReplBatch     int32 `mapstructure:"replBatch"     default:"100"`
}

type ServerConfig struct {
	LogLevel         string `mapstructure:"logLevel"         default:"info"  validate:"oneof=debug info warn error fatal"`
	EnableDBQueryLog bool   `mapstructure:"enableDBQueryLog" default:"false"`
	EnableAuditLog   bool   `mapstructure:"enableAuditLog"   default:"true"`
	EnableLarkMsg    bool   `mapstructure:"enableLarkMsg"    default:"true"`
	Stage            string `mapstructure:"stage"            default:"prod"`

	StashingAreaBucketPrefix string `mapstructure:"stashingAreaBucketPrefix"`

	GrpcPort       int    `mapstructure:"grpcPort"       default:"10000"`
	MetricsPort    int    `mapstructure:"metricsPort"    default:"9090"`
	HttpListenAddr string `mapstructure:"httpListenAddr" default:":8080"`

	SaltSecret  string        `mapstructure:"saltSecret"  validate:"required"`
	SecretStore *SecretConfig `mapstructure:"secret"`
	RBACEnabled bool          `mapstructure:"rbacEnabled"                     default:"true"`
}

type WorkerConfig struct {
	RegionalConfig          []RegionalConfig `mapstructure:"regionConfig"            validate:"required"`
	DisableCanaryController bool             `mapstructure:"disableCanaryController"`
}
type K8sConfig struct {
	Kubernetes                    string `mapstructure:"kubernetes"                    validate:"required"`
	O11yUrl                       string `mapstructure:"o11yUrl"                       validate:"required"`
	ClusterProxyUrl               string `mapstructure:"clusterProxyUrl"               validate:"required"`
	K8sIngressUrl                 string `mapstructure:"k8sIngressUrl"                 validate:"required"`
	ForceNewClustersToCanaryPool  bool   `mapstructure:"forceNewClustersToCanaryPool"`  // only for new starter clusters to canary pool
	ForceNewEssentialToCanaryPool bool   `mapstructure:"forceNewEssentialToCanaryPool"` // only for new essential clusters to canary pool
	DisableCanaryToStandardPool   bool   `mapstructure:"disableCanaryToStandardPool"`
}
type RegionalConfig struct {
	Provider           string      `mapstructure:"provider"           validate:"required"`
	Region             string      `mapstructure:"region"             validate:"required"`
	RUPrice            string      `mapstructure:"ruPrice"            validate:"required"`
	RCUPrice           string      `mapstructure:"rcuPrice"`
	RowStoragePrice    string      `mapstructure:"rowStoragePrice"    validate:"required"`
	ColumnStoragePrice string      `mapstructure:"columnStoragePrice" validate:"required"`
	K8sConfig          []K8sConfig `mapstructure:"k8sConfig"          validate:"required"`
}

type SvcAddrConfig struct {
	DataflowSvcAddr         string `mapstructure:"dataflowSvcAddr"         default:"dataflow-grpc:10000"`
	AclSvcAddr              string `mapstructure:"aclSvcAddr"              default:"account-acl-grpc:10000"`
	AccountSvcAddr          string `mapstructure:"accountSvcAddr"          default:"account-provider-grpc:10000"`
	BillingAPIAddr          string `mapstructure:"billingApiAddr"          default:"billing2-api:18888"`
	BillingMeasuresAddr     string `mapstructure:"billingMeasuresAddr"     default:"billing2:18888"`
	PCAArn                  string `mapstructure:"pcaArn"                                                                                    validate:"required"`
	CMKArn                  string `mapstructure:"cmkArn"                                                                                    validate:"required"`
	ManagementSvcAddr       string `mapstructure:"managementSvcAddr"       default:"tidb-mgmt-service.tidb-management-service:10001"`
	ServerlessGlobalSvcAddr string `mapstructure:"serverlessGlobalSvcAddr" default:"serverless-global-service.tidb-management-service:10000"`
	ConfigCenterAddr        string `mapstructure:"configCenterAddr"        default:"config-center.devops:8080"`
}

type SecretConfig struct {
	SecretKey string `mapstructure:"awsSecretId"`
	Region    string `mapstructure:"region"`
}

type GardenerConfig struct {
	GardenerProjectName string `mapstructure:"gardenerProjectName" default:"core" validate:"required"`
}

// InfraAPIConfig is the configration items for infra API operations.
type InfraAPIConfig struct {
	// kubeconfig file for the infra API server, ignore this to use the default secret
	KubeConfigPath string `mapstructure:"kubeConfigPath"`

	// ref https://github.com/tidbcloud/infra-api/pull/79
	// Enable this to keep the 'key' of resources do not change in infra-api.
	// will use projectID@dbaas as projectID@infra-api
	ProjectIDAsInfraProjectID bool `mapstructure:"projectIDAsInfraProjectID"`
}

type Auth0Config struct {
	Auth0Domain   string `mapstructure:"auth0Domain"   validate:"required"`
	Auth0Audience string `mapstructure:"auth0Audience" validate:"required"`
	Auth0Secret   string `mapstructure:"auth0Secret"   validate:"required"`

	ManagementPortalAuth0ClientAudience string `mapstructure:"managementPortalAuth0ClientAudience" validate:"required"`

	ProxyAuth0Domain       string `mapstructure:"proxyAuth0Domain"       validate:"required"`
	ProxyAuth0Audience     string `mapstructure:"proxyAuth0Audience"     validate:"required"`
	ProxyAuth0ClientID     string `mapstructure:"proxyAuth0ClientID"     validate:"required"`
	ProxyAuth0ClientSecret string `mapstructure:"proxyAuth0ClientSecret" validate:"required"`
}

type MysqlConfig struct {
	Host     string `mapstructure:"host"     default:"host"`
	User     string `mapstructure:"user"     default:"root"`
	Port     uint16 `mapstructure:"port"     default:"4000"`
	Password string `mapstructure:"password"                 validate:"required"`
	Database string `mapstructure:"database" default:"dbaas"`
}

type BizConfig struct {
	ServerlessFreeQuotaMaxCount int32 `mapstructure:"serverlessFreeQuotaMaxCount"           default:"5"`
	MaxBranchNumber             int64 `mapstructure:"maxBranchNumber"                       default:"5"`
	// Default free branch storage limit is 10GiB
	BranchStorageLimitMiB int64 `mapstructure:"branchStorageLimitMiB"                 default:"10240"`
	// Default scalable branch storage limit is 100GiB
	BranchStorageLimitMiBVip        int64            `mapstructure:"branchStorageLimitMiBVip"              default:"102400"`
	BranchParentSizeUpperLimitBytes int64            `mapstructure:"branchParentSizeUpperLimitBytes"`
	BranchTenantNumberQuota         map[uint64]int64 `mapstructure:"branchTenantNumberQuota"`
	// BranchOmitParentTimestampKuberneteses lists kubernetes clusters where
	// branches created without an explicit point in time omit the
	// parent-timestamp annotation (letting the downstream manager pick the
	// online snapshot restore path). Unlisted clusters keep the legacy
	// behavior of defaulting the timestamp to now.
	BranchOmitParentTimestampKuberneteses []string           `mapstructure:"branchOmitParentTimestampKuberneteses"`
	ImportTenantCompressRatio             map[string]float64 `mapstructure:"importTenantCompressRatio"`
	BillingLargeTenants                   []uint64           `mapstructure:"billingLargeTenants"`
	// SpendLimitMaxOverrideTenants maps org ID to a higher monthly spending limit ceiling in cents.
	// Orgs not listed use constant.MaxSpendLimitValue ($10000). Example: 1372813089209301246: 50000000 ($500000).
	SpendLimitMaxOverrideTenants map[uint64]uint32 `mapstructure:"spendLimitMaxOverrideTenants"`

	MaxDMTaskNumberPerOrg      int64            `mapstructure:"maxDMTaskNumberPerOrg"      default:"100"`
	DMTaskOrgNumberQuota       map[string]int64 `mapstructure:"dmTaskOrgNumberQuota"`
	MaxExportConcurrencyPerOrg int64            `mapstructure:"maxExportConcurrencyPerOrg" default:"100" validate:"gte=1"`

	ExportSyncerConcurrency            int      `mapstructure:"exportSyncerConcurrency"            default:"20" validate:"gte=1"`
	SkipExportSizeCheckTenants         []uint64 `mapstructure:"skipExportSizeCheckTenants"`
	SkipExportLocalStorageCheckTenants []uint64 `mapstructure:"skipExportLocalStorageCheckTenants"`

	IndexAdvisorNotificationTriggerId  string `mapstructure:"indexAdvisorNotificationTriggerId"  default:"new-index-advisor-tip"`
	IndexAdvisorNotificationWorkflowId string `mapstructure:"indexAdvisorNotificationWorkflowId" default:"66d1ae0f09509e752e090607"`

	BackupScheduleCheckDate   string   `mapstructure:"backupScheduleCheckDate"   default:""`
	MigrationWhitelistTenants []uint64 `mapstructure:"migrationWhitelistTenants"`
	MigrationWhitelistRegions []string `mapstructure:"migrationWhitelistRegions"`

	SkipSetClusterStatusTenants []uint64 `mapstructure:"skipSetClusterStatusTenants"`

	// Instance Capacity contracted org configuration
	InstanceCapacityContracts []InstanceCapacityContract `mapstructure:"instanceCapacityContracts"`
}

// InstanceCapacityContract defines a contracted org for instance capacity billing.
type InstanceCapacityContract struct {
	OrgID            uint64 `mapstructure:"orgID"`
	FreeTierOverride int64  `mapstructure:"freeTierOverride"` // optional, default uses constant.InstanceCapacityFreeLimit
	ExcludeBranches  bool   `mapstructure:"excludeBranches"`  // if true, branch instances are not counted towards instance capacity billing
}

// MaxSpendLimitForOrg returns the monthly spending limit ceiling in cents for the given org.
// Override entries must be > 0; otherwise the default MaxSpendLimitValue is used.
func (c *BizConfig) MaxSpendLimitForOrg(orgID uint64) uint32 {
	if c.SpendLimitMaxOverrideTenants != nil {
		if limit, ok := c.SpendLimitMaxOverrideTenants[orgID]; ok && limit > 0 {
			return limit
		}
	}
	return constant.MaxSpendLimitValue
}

/*
 * For serverless service, we use white list to control feature gates.
 * For example, if you want to enable a feature gate for tenant 1, 2, 3, you can set:
 * featureGates:
 *   the_feature_gate_name:
 *     enabledTenants: ["1", "2", "3"]
 *
 * if you want to enable the feature gate for all tenants, you can set:
 * featureGates:
 *   the_feature_gate_name:
 *     enabledTenants: ["*"]
 *
 * Note: 'the_feature_gate_name' MUST be the same as the name in internal/domain/constant/featuregate.go
 * Note: the value in the list of 'enabledTenants' MUST be string, !!NOT!! integer
 * Note: if you want to disable the feature gate, just remove the feature gate config
 * Note: if "*" appears in the list of 'enabledTenants', all other values will be ignored
 */
type FeatureGate struct {
	EnabledTenants []string `mapstructure:"enabledTenants"`
}

type SendGridConfig struct {
	APIKey string `mapstructure:"apiKey"`
}

type OssConfig struct {
	AccessKeyID     string `mapstructure:"accessKeyID"`
	SecretAccessKey string `mapstructure:"secretAccessKey"`
}

type AccessKey struct {
	AccessKeyID     string `mapstructure:"accessKeyID"`
	SecretAccessKey string `mapstructure:"secretAccessKey"`
}
