package config

import (
	"encoding/base64"
	"fmt"
	"os"
	"reflect"
	"strings"

	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/yaml"
)

type RootConfig struct {
	ControllerConfig `json:",inline"`

	// Namespace is the namespace  of objects managed by dataplane manager
	Namespace                     string                        `json:"namespace" default:"dataplane-metadata"`
	Predicate                     metav1.LabelSelector          `json:"predicate"`
	DNSProviderConfig             DNSProviderConfig             `json:"dnsProviderConfig"`
	MeteringStorageProviderConfig MeteringStorageProviderConfig `json:"meteringStorageProviderConfig"`
	// ResourceLabels will be added into 'metadata.labels' for every resource created by the regionalserver API.
	ResourceLabels     map[string]string `json:"resourceLabels,omitempty"`
	ResourceNamePrefix string            `json:"resourceNamePrefix" default:"tidbcloud"`
	// AdditionalCloudTags will be added to the all cloud resources created by dataplane manager
	// For BYOC, customer will restrict us to only manage resources with specific tags
	AdditionalCloudTags map[string]string `json:"additionalCloudTags,omitempty"`

	BYOC BYOCConfig `json:"byoc"`
	O11Y O11YConfig `json:"o11y,omitempty"`

	NetworkCapacity NetworkCapacityConfig `json:"networkCapacity,omitempty"`

	ObjectStorageMetrics ObjectStorageMetricsConfig `json:"objectStorageMetrics,omitempty"`

	// AccountListPath is the path to the account list config file.
	AccountListPath string `json:"accountListPath,omitempty"`
}

type ObjectStorageMetricsConfig struct {
	RefreshInterval       string                       `json:"refreshInterval,omitempty"`
	AWSStorageLensReports []AWSStorageLensReportConfig `json:"awsStorageLensReports,omitempty"`
}

type AWSStorageLensReportConfig struct {
	Name         string `json:"name"`
	Region       string `json:"region"`
	AccountID    string `json:"accountID"`
	ConfigID     string `json:"configID"`
	ReportBucket string `json:"reportBucket"`
	ReportPrefix string `json:"reportPrefix,omitempty"`
	RoleARN      string `json:"roleARN,omitempty"`
}

const defaultReservedAdminPodIPs int64 = 50

type NetworkCapacityConfig struct {
	ReservedAdminPodIPs *int64 `json:"reservedAdminPodIPs,omitempty"`
}

func (c RootConfig) EffectiveReservedAdminPodIPs() int64 {
	if c.NetworkCapacity.ReservedAdminPodIPs == nil {
		return defaultReservedAdminPodIPs
	}
	return *c.NetworkCapacity.ReservedAdminPodIPs
}

type O11YConfig struct {
	VPCID     string `json:"vpcID,omitempty"`
	VPCCIDR   string `json:"vpcCIDR,omitempty"`
	AccountID string `json:"accountID,omitempty"`
	Region    string `json:"region,omitempty"`
}

type ResourceTaggerConfig struct {
	// Enabled controls whether the resource tagger is active.
	Enabled bool `json:"enabled"`

	// ScanInterval defines how frequently to scan dataplane k8s clusters for tagging.
	ScanInterval string `json:"scanInterval"`
}

type ControllerConfig struct {
	Provider       string         `json:"provider"`
	Env            string         `json:"env"`
	RegionConfig   RegionConfig   `json:"regionConfig"`
	InternetConfig InternetConfig `json:"internetConfig"`
	APPBoxConfig   APPBoxConfig   `json:"appboxConfig"`
	// For BYOC, we need to create a PrivateLink endpoint in the control VPC.
	ControlPlaneCrossplaneProviderConfig string `json:"controlPlaneCrossplaneProviderConfig"`
	// We need to create some AWS resources in other cloud vendors, e.g. AWS OIDC provider for AliCloud.
	// It should use the AWS account as the name of the provider config.
	AWSCrossplaneProviderConfig string `json:"awsCrossplaneProviderConfig"`
	// DataPlaneRoleName is the name of the role that the crossplane or dataplane manager assume to access the cloud provider or data plane k8s
	DataPlaneRoleName string `json:"dataPlaneRoleName"`
	// AWSDataPlaneExternalID is the external id to assume to DataPlaneRoleName in AWS
	AWSDataPlaneExternalID string               `json:"awsDataPlaneExternalID"`
	ResourceTaggerConfig   ResourceTaggerConfig `json:"resourceTaggerConfig"`
	// DisableProxyProtocol indicates whether to disable proxy protocol for load balancers.
	// Default is false, which means proxy protocol is enabled by default.
	DisableProxyProtocol bool `json:"disableProxyProtocol"`

	// ACRResourceGroup is the resource group of the Azure Container Registry.
	ACRResourceGroup string `json:"acrResourceGroup"`

	// KMSConfig contains Aliyun KMS settings for ACK CMEK support.
	KMSConfig KMSConfig `json:"kmsConfig,omitempty"`
}

func (c RootConfig) AzureMeteringStorageScope() string {
	if c.MeteringStorageProviderConfig.Provider != AzureBlobMeteringStorageProvider {
		return ""
	}

	return strings.TrimSpace(c.MeteringStorageProviderConfig.AzureBlob.Scope)
}

// KMSConfig contains the pre-provisioned Aliyun KMS instance settings for a region.
// Required for ACK provider when CMEK is enabled.
type KMSConfig struct {
	// AccountID is the Aliyun account ID that owns the KMS instance.
	// Typically the control plane account. Used to build key policy and manager role ARN.
	AccountID string `json:"accountID"`
	// InstanceID is the pre-provisioned KMS instance ID.
	InstanceID string `json:"instanceID"`
}

type InternetConfig struct {
	OSSGatewayEndpointPolicyTemplateFile string `json:"ossGatewayEndpointPolicyTemplateFile"`
	// EnableECRInterfaceEndpoints controls whether the EKS internet controller
	// manages private ECR interface endpoints (`ecr.api` and `ecr.dkr`).
	EnableECRInterfaceEndpoints bool `json:"enableECRInterfaceEndpoints"`
	// ECRServiceRegion controls which AWS region's ECR service the interface endpoints target.
	// When empty, the dataplane internet region is used.
	ECRServiceRegion string `json:"ecrServiceRegion,omitempty"`

	// AliCloudCommonBandwidthPackageIDMap maps from account to the common bandwidth package ID.
	// For each dataplane account and each region, we will create a common bandwidth package.
	AliCloudCommonBandwidthPackageIDMap map[string]string `json:"aliCloudCommonBandwidthPackageIDMap"`
}

type APPBoxConfig struct {
	ServiceRoleName             string               `json:"serviceRoleName"`
	ACR                         ACRConfig            `json:"acr"`
	AdminNodeConfig             AdminNodeConfig      `json:"adminNodeConfig"`
	WorkerNodeConfig            WorkerNodeConfig     `json:"workerNodeConfig"`
	NodePools                   []NodePoolConfig     `json:"nodePools"`
	SharedNodePools             []NodePoolConfig     `json:"sharedNodePools"`
	O11yNodeConfig              O11yNodeConfig       `json:"o11yNodeConfig"`
	EnableK8sPublicAccess       bool                 `json:"enableK8sPublicAccess"`
	ApiserverPublicAccessCidrs  []string             `json:"apiserverPublicAccessCidrs"`
	ApiGateway                  ApiGatewayConfig     `json:"apiGateway"`
	K8sVersion                  string               `json:"k8sVersion"`
	K8sServiceCidr              string               `json:"k8sServiceCidr"`
	AuthorizedSubjects          []string             `json:"authorizedSubjects"`
	ClusterAdmins               []ClusterAdminConfig `json:"clusterAdmins"`
	NodeUserDataConfig          string               `json:"nodeUserDataConfig"`
	LocalDiskNodeUserDataConfig string               `json:"localDiskNodeUserDataConfig"`
	AdminNodeRoleConfig         NodeRoleConfig       `json:"adminNodeRoleConfig"`
	O11yBizType                 string               `json:"o11yBizType"`
	O11yAgentNamespace          string               `json:"o11yAgentNamespace"`
	PCAConfig                   *PCAConfig           `json:"pcaConfig"`
	CSIDriver                   *CSIDriverConfig     `json:"csiDriver,omitempty"`
	// EnableO11yVectorHostNetwork enables host networking for the o11y Vector
	// DaemonSet in every dataplane in this region.
	EnableO11yVectorHostNetwork bool `json:"enableO11yVectorHostNetwork"`
	// DaemonSetRequests specifies CPU requests for built-in DaemonSets in ACK clusters.
	// This configuration allows reducing CPU requests to ensure our workloads can be scheduled
	// successfully while still providing sufficient resources for system DaemonSets to run stably.
	// Key: DaemonSet name in kube-system namespace
	// Value: CPU request value in Kubernetes quantity format (e.g., "100m", "50m")
	DaemonSetRequests map[string]string `json:"daemonSetRequests,omitempty"`

	// AdminGroupObjectIDs is the object ids of the admin group in the Azure AD.
	AdminGroupObjectIDs []string `json:"adminGroupObjectIDs"`

	// DisableClusterProxy controls whether to skip ClusterProxy deployment.
	// When set to true, existing ClusterProxy will be uninstalled.
	// Default is false (ClusterProxy is deployed).
	DisableClusterProxy bool `json:"disableClusterProxy"`
}

type PCAConfig struct {
	CAIdentifier string `json:"caIdentifier"`
	Region       string `json:"region"`
	// AssumeRoleArn is the role ARN that has permission to issue certs from the PCA.
	// Because the PCA issuer deploy in the dataplane,
	// we need to assume to controlplane role to issue certs.
	AssumeRoleArn string `json:"assumeRoleARN"`
	// IssuerPolicyTemplateFile is the path of the IAM policy template file for the PCA issuer.
	IssuerPolicyTemplateFile string `json:"issuerPolicyTemplateFile"`
}

type ACRConfig struct {
	InstanceId    string `json:"instanceId"`
	Region        string `json:"region"`
	Domains       string `json:"domains"`
	AssumeRoleARN string `json:"assumeRoleARN"`
}

type ApiGatewayConfig struct {
	EnablePublicAccess        bool     `json:"enablePublicAccess"`
	PublicAccessCidrs         []string `json:"publicAccessCidrs"`
	DisableDeletionProtection bool     `json:"disableDeletionProtection"`
}

// CSIDriverConfig contains custom image configurations for ACK CSI driver components.
// If this config is set, the CSI driver deployment will be modified to use custom images
// that support VolumeAttributesClass feature.
type CSIDriverConfig struct {
	// DisableEncryption indicates whether to disable encryption for storageclass
	DisableEncryption bool `json:"disableEncryption"`
	// ProvisionerImage is the custom image for csi-provisioner container
	ProvisionerImage string `json:"provisionerImage"`
	// DiskResizerImage is the custom image for external-disk-resizer container
	DiskResizerImage string `json:"diskResizerImage"`
	// PluginImage is the custom image for csi-plugin container
	PluginImage string `json:"pluginImage"`
	// ExternalProvisionerImage is the custom image for external-disk-provisioner container
	ExternalProvisionerImage string `json:"externalProvisionerImage"`
}

// ClusterAdminConfig represents the IAM role that has k8s cluster admin permission
type ClusterAdminConfig struct {
	ID            string `json:"id"`
	IAMRoleARN    string `json:"iamRoleARN"`
	InDataAccount bool   `json:"inDataAccount"`
}

type NodeRoleConfig struct {
	// ContainerRegistryPolicyTemplateFile is the path of the policy template file for pulling images from container registry.
	ContainerRegistryPolicyTemplateFile string `json:"containerRegistryPolicyTemplateFile"`
	// LoadBalancerControllerPolicyTemplateFile is the path of the policy template file for the load balancer controller.
	LoadBalancerControllerPolicyTemplateFile string `json:"loadBalancerControllerPolicyTemplateFile"`
	// ClusterAutoscalerPolicyTemplateFile is the path of the policy template file for the cluster autoscaler.
	ClusterAutoscalerPolicyTemplateFile string `json:"clusterAutoscalerPolicyTemplateFile"`
	// CertManagerPolicyTemplateFile is the path of the policy template file for the cert-manager
	CertManagerPolicyTemplateFile string `json:"certManagerPolicyTemplateFile"`
	// TidbClusterPolicyTemplateFile is the path of the policy template file for the tidb cluster.
	TidbClusterPolicyTemplateFile string `json:"tidbClusterPolicyTemplateFile"`
	// O11yAgentPolicyTemplateFile is the path of the policy template file for the o11y role.
	O11yAgentPolicyTemplateFile string `json:"o11yAgentPolicyTemplateFile"`
	// ExternalDNSPolicyTemplateFile is the path of the policy template file for the external dns controller.
	ExternalDNSPolicyTemplateFile string `json:"externalDNSPolicyTemplateFile"`
	// ManagedPolicies is the list of managed policies that will be attached to the role.
	ManagedPolicies []string `json:"managedPolicies"`
}

// O11yNodeConfig configures optional dedicated o11y nodes managed by APPBox reconcile
// (same lifecycle path as admin nodes; not via DataPlane tidb.NodePool CR).
// Source: root-config only (no APPBox CR override).
type O11yNodeConfig struct {
	// Enabled controls whether o11y node pools/groups are created.
	// When false, any previously created o11y pools owned by the APPBox are deleted.
	Enabled bool `json:"enabled"`

	// Pools is the list of o11y node pool specs. Each entry creates one pool/group.
	// When Enabled is true but Pools is empty, no o11y pools are created and any
	// previously created ones are cleaned up.
	Pools []O11yNodePoolConfig `json:"pools,omitempty"`
}

// O11yNodePoolConfig is one o11y node pool/group spec inside O11yNodeConfig.Pools.
type O11yNodePoolConfig struct {
	// AMIType specifies the AMI type for o11y nodes (EKS only; e.g. bottlerocket).
	AMIType string `json:"amiType,omitempty"`

	// SystemVolumeSize is the GiB size of volume used for storing container images and logs.
	SystemVolumeSize *int64 `json:"systemVolumeSize,omitempty"`

	// InstanceType specifies the instance type for this o11y node pool/group.
	InstanceType string `json:"instanceType"`

	// MinSize specifies the minimum number of o11y nodes. Provider default is 0.
	MinSize *int32 `json:"minSize,omitempty"`

	// MaxSize specifies the maximum number of o11y nodes.
	MaxSize *int32 `json:"maxSize,omitempty"`

	// CPU is the vCPU count used for the node-spec label (e.g. 2 → "2C4G").
	CPU int `json:"cpu"`

	// MemoryGi is the memory in GiB used for the node-spec label.
	MemoryGi int `json:"memoryGi"`
}

// NodePoolConfig is the worker nodepool config
// regional-server will create nodepools based on instance type, AZ, k8s version
//
// Placement group eligibility is auto-derived from the number of node zones:
// single-AZ (ZONAL) pools get an EC2 partition placement group; multi-AZ (REGIONAL)
// pools do not. There is no per-pool override.
type NodePoolConfig struct {
	InstanceType string `json:"instanceType"`
	// SystemVolumeSize is the GiB size of volume used for storing container images and logs.
	SystemVolumeSize int64 `json:"systemVolumeSize"`
	// https://docs.aws.amazon.com/eks/latest/APIReference/API_Nodegroup.html
	AMIType      string `json:"amiType,omitempty"`
	CPU          int    `json:"cpu"`
	AvailableCPU int    `json:"availableCpu"`
	// AvailableCPUMilli is allocatable minus DaemonSet CPU request, in millicores.
	// More precise than AvailableCPU (whole cores). Unset (0) falls back to AvailableCPU*1000.
	AvailableCPUMilli int `json:"availableCpuMilli,omitempty"`
	// MaxCPUMilli is the CPU resource limit in millicores.
	// Generally AvailableCPUMilli <= MaxCPUMilli <= allocatable. Unset (0) falls back to available milli.
	MaxCPUMilli       int `json:"maxCpuMilli,omitempty"`
	MemoryGi          int `json:"memoryGi"`
	AvailableMemoryMi int `json:"availableMemoryMi"`
	// MaxMemoryMi is the memory resource limit in MiB.
	// Generally AvailableMemoryMi <= MaxMemoryMi <= allocatable. Unset (0) falls back to AvailableMemoryMi.
	MaxMemoryMi   int  `json:"maxMemoryMi,omitempty"`
	HasLocalDisk  bool `json:"hasLocalDisk"`
	LocalDiskSize int  `json:"localDiskSize"`
}

const (
	DefaultWorkerSystemVolumeSize = 50
)

// WorkerNodeConfig defines the default node config for all workers.
// It can be overrided by NodePoolConfig for different instance types
type WorkerNodeConfig struct {
	// SystemVolumeSize is the GiB size of volume used for storing container images and logs.
	SystemVolumeSize int64 `json:"systemVolumeSize,omitempty"`
}

func (wc *WorkerNodeConfig) GetSystemVolumeSize() int64 {
	if wc.SystemVolumeSize == 0 {
		return DefaultWorkerSystemVolumeSize
	}

	return wc.SystemVolumeSize
}

// AdminNodeConfig contains configuration for admin (shared) node pools/groups.
// This is used for the initial admin nodes that run system components.
// Worker nodes use the separate NodePoolConfig structure.
//
// Configuration priority (per field):
// 1. APPBox CR spec (APPBox.Spec.KubernetesConfig.AdminNodeConfig)
// 2. Config file (this struct)
// 3. Provider-specific defaults
//
// Note: The APPBox CR is typically created by dataplane-controller, but operators
// may manually set AdminNodeConfig fields for specific instances. The dataplane-manager
// must preserve these manual settings during reconciliation.
type AdminNodeConfig struct {
	// AMIType specifies the AMI type for admin nodes
	// Currently only used by EKS
	AMIType string `json:"amiType"`

	// SystemVolumeSize is the GiB size of volume used for storing container images and logs
	SystemVolumeSize *int64 `json:"systemVolumeSize,omitempty"`

	// InstanceType specifies the default instance type for admin nodes
	// If not set, provider-specific defaults will be used
	InstanceType *string `json:"instanceType,omitempty"`

	// MinSize specifies the minimum number of admin nodes
	// If not set, provider-specific defaults will be used
	MinSize *int32 `json:"minSize,omitempty"`

	// MaxSize specifies the maximum number of admin nodes
	// If not set, provider-specific defaults will be used
	MaxSize *int32 `json:"maxSize,omitempty"`

	// AdditionalInstanceTypes specifies extra instance types for admin node groups.
	// For each instance type in this list, a separate node group is created with minSize=0
	// to allow the cluster autoscaler to scale up when the primary instance type is unavailable.
	AdditionalInstanceTypes []string `json:"additionalInstanceTypes,omitempty"`
}

type RegionConfig struct {
	LogicalRegionName string   `json:"logicalRegionName"`
	Name              string   `json:"name"`
	Zones             []string `json:"zones"`
	NodeZones         []string `json:"nodeZones,omitempty"`
}

func LoadRootConfig(path string) (RootConfig, error) {
	return LoadRootConfigs([]string{path})
}

func LoadRootConfigs(paths []string) (RootConfig, error) {
	if len(paths) == 0 {
		return RootConfig{}, fmt.Errorf("root config path is empty")
	}

	merged := make(map[string]interface{})
	for _, path := range paths {
		if strings.TrimSpace(path) == "" {
			return RootConfig{}, fmt.Errorf("root config path is empty")
		}

		fileConfig, err := loadRootConfigMap(path)
		if err != nil {
			return RootConfig{}, err
		}
		mergeConfigMap(merged, fileConfig)
	}

	data, err := yaml.Marshal(merged)
	if err != nil {
		return RootConfig{}, err
	}

	var config RootConfig
	if err = yaml.Unmarshal(data, &config); err != nil {
		return RootConfig{}, err
	}
	if err = validateRootConfig(config); err != nil {
		return RootConfig{}, err
	}

	return config, nil
}

func loadRootConfigMap(path string) (map[string]interface{}, error) {
	//nolint:gosec // path is from configuration file, not user input
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	config := make(map[string]interface{})
	if err = yaml.Unmarshal(data, &config); err != nil {
		return nil, err
	}

	return config, nil
}

func validateRootConfig(config RootConfig) error {
	if err := config.ControllerConfig.RegionConfig.ValidateNodeZones(); err != nil {
		return err
	}
	if config.EffectiveReservedAdminPodIPs() < 0 {
		return fmt.Errorf("networkCapacity.reservedAdminPodIPs must not be negative")
	}

	return nil
}

func mergeConfigMap(dst, src map[string]interface{}) {
	for key, srcValue := range src {
		srcMap, srcIsMap := toStringMap(srcValue)
		dstMap, dstIsMap := toStringMap(dst[key])
		if srcIsMap && dstIsMap {
			mergeConfigMap(dstMap, srcMap)
			dst[key] = dstMap
			continue
		}

		dst[key] = srcValue
	}
}

func toStringMap(value interface{}) (map[string]interface{}, bool) {
	if typed, ok := value.(map[string]interface{}); ok {
		return typed, true
	}

	rv := reflect.ValueOf(value)
	if !rv.IsValid() || rv.Kind() != reflect.Map || rv.Type().Key().Kind() != reflect.String {
		return nil, false
	}

	result := make(map[string]interface{}, rv.Len())
	iter := rv.MapRange()
	for iter.Next() {
		result[iter.Key().String()] = iter.Value().Interface()
	}
	return result, true
}

func (c RegionConfig) EffectiveNodeZones() []string {
	if len(c.NodeZones) > 0 {
		return append([]string(nil), c.NodeZones...)
	}
	return append([]string(nil), c.Zones...)
}

func (c RegionConfig) ValidateNodeZones() error {
	if len(c.NodeZones) == 0 {
		return nil
	}

	if len(c.Zones) == 0 {
		return fmt.Errorf("regionConfig.zones must be configured when regionConfig.nodeZones is set")
	}

	allowedZones := make(map[string]struct{}, len(c.Zones))
	for _, zone := range c.Zones {
		allowedZones[zone] = struct{}{}
	}

	for _, zone := range c.NodeZones {
		if _, ok := allowedZones[zone]; !ok {
			return fmt.Errorf("regionConfig.nodeZones contains zone %q not in regionConfig.zones", zone)
		}
	}

	if len(c.NodeZones) >= len(c.Zones) {
		return fmt.Errorf("regionConfig.nodeZones must be a strict subset of regionConfig.zones")
	}

	return nil
}

func (c RootConfig) ShouldProvisionPrivateLinkEndpointSubnets(nodeZones []string) bool {
	if c.BYOC.BYOCPrivateLink.IsEnabled() {
		return false
	}
	if c.Provider != consts.ProviderTypeEKS && c.Provider != consts.ProviderTypeACK {
		return false
	}
	return len(nodeZones) == 1
}

func (c *RootConfig) LoadNodeUserData() (string, error) {
	path := c.APPBoxConfig.NodeUserDataConfig
	if path == "" {
		// omit if no userdata config is configured
		return "", nil
	}
	//nolint:gosec // path is from configuration file, not user input
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(data), nil
}

func (c *RootConfig) LoadLocalDiskNodeUserData() (string, error) {
	path := c.APPBoxConfig.LocalDiskNodeUserDataConfig
	if path == "" {
		// omit if no userdata config is configured
		return "", nil
	}
	//nolint:gosec // path is from configuration file, not user input
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(data), nil
}

type BYOCConfig struct {
	// config for PrivateLink between the regional control plane and the customer VPC cluster gateway.
	BYOCPrivateLink BYOCPrivateLinkConfig `json:"privateLink"`
	NodeRoleName    string                `json:"nodeRoleName"`
	ImportRoleName  string                `json:"importRoleName"`
	// AzureAPIServerDNSZone is the shared private DNS zone that stores AKS API server
	// private endpoint A records for Azure BYOC control-plane to dataplane connectivity.
	AzureAPIServerDNSZone AzureDNSZoneConfig `json:"azureAPIServerDNSZone"`
	// AzureGatewayDNSZone is the shared private DNS zone that stores Kong gateway
	// private endpoint A records for Azure BYOC control-plane to dataplane connectivity.
	AzureGatewayDNSZone AzureDNSZoneConfig `json:"azureGatewayDNSZone"`
	// AKSControlPlaneIdentityName is the customer-managed user-assigned identity name used by the AKS control plane.
	// require `Network Contributor` in subscription level and `Managed Identity Operator` in the AKSKubeletIdentity
	AKSControlPlaneIdentityName string `json:"aksControlPlaneIdentityName"`
	// AKSKubeletIdentityName is the customer-managed user-assigned identity name used by AKS kubelets.
	// require `Storage Blob Data Owner` in subscription level and `AcrPull` on specific ACR.
	AKSKubeletIdentityName string `json:"aksKubeletIdentityName"`
	// AKSManagedIdentityResourceGroupName is the Azure resource group that contains the customer-managed identities
	// referenced by AKSControlPlaneIdentityName and AKSKubeletIdentityName.
	AKSManagedIdentityResourceGroupName string `json:"aksManagedIdentityResourceGroupName"`
}

func (c RootConfig) ShouldBypassDynamicKeyspaceRole() bool {
	if c.BYOC.BYOCPrivateLink.IsEnabled() {
		return true
	}
	return strings.TrimSpace(c.BYOC.NodeRoleName) != "" || strings.TrimSpace(c.BYOC.ImportRoleName) != ""
}

type AKSManagedIdentityConfig struct {
	ControlPlaneIdentityName     string
	KubeletIdentityName          string
	ManagedIdentityResourceGroup string
}

func (c BYOCConfig) AKSManagedIdentityConfig() (*AKSManagedIdentityConfig, bool, error) {
	controlPlaneIdentityName := strings.TrimSpace(c.AKSControlPlaneIdentityName)
	kubeletIdentityName := strings.TrimSpace(c.AKSKubeletIdentityName)
	managedIdentityResourceGroupName := strings.TrimSpace(c.AKSManagedIdentityResourceGroupName)

	if controlPlaneIdentityName == "" && kubeletIdentityName == "" && managedIdentityResourceGroupName == "" {
		return nil, false, nil
	}
	if controlPlaneIdentityName == "" || kubeletIdentityName == "" || managedIdentityResourceGroupName == "" {
		return nil, false, fmt.Errorf(
			"byoc.aksControlPlaneIdentityName, byoc.aksKubeletIdentityName and byoc.aksManagedIdentityResourceGroupName must be configured together",
		)
	}

	return &AKSManagedIdentityConfig{
		ControlPlaneIdentityName:     controlPlaneIdentityName,
		KubeletIdentityName:          kubeletIdentityName,
		ManagedIdentityResourceGroup: managedIdentityResourceGroupName,
	}, true, nil
}

func (c RootConfig) IsAKSBYOCStaticAuthEnabled() (bool, error) {
	if strings.TrimSpace(c.Provider) != consts.ProviderTypeAKS {
		return false, nil
	}

	_, enabled, err := c.BYOC.AKSManagedIdentityConfig()
	if err != nil {
		return false, err
	}
	return enabled, nil
}

type BYOCPrivateLinkConfig struct {
	// a PrivateLink endpoint will be created in this control-plane VPC/VNet.
	ControlPlaneVPCID string `json:"controlPlaneVPCID"`
	// the subnets should in the same physical network as the cluster gateway.
	ControlPlaneSubnetIDs []string `json:"controlPlaneSubnetIDs"`
	// at least one security group should be specified.
	// and the security group should have an inbound rule from the regional server (cluster service) CIDR.
	// we may use the "default" security group of the VPC here.
	ControlPlaneSecurityGroupIDs []string `json:"controlPlaneSecurityGroupIDs"`
	// Azure resource group used for control-plane Private Endpoint and Private DNS resources.
	ControlPlaneResourceGroup string `json:"controlPlaneResourceGroup,omitempty"`
}

func (c BYOCPrivateLinkConfig) IsEnabled() bool {
	return strings.TrimSpace(c.ControlPlaneVPCID) != ""
}

func (c BYOCPrivateLinkConfig) IsAzureEnabled() bool {
	return strings.TrimSpace(c.ControlPlaneVPCID) != "" &&
		len(c.ControlPlaneSubnetIDs) > 0 &&
		strings.TrimSpace(c.ControlPlaneResourceGroup) != ""
}

type DNSProvider string

const (
	AwsRoute53   DNSProvider = "awsRoute53"
	AzureDNSZone DNSProvider = "azureDNSZone"
	GcpCloudDNS  DNSProvider = "gcpCloudDNS"
	AliCloudDNS  DNSProvider = "aliCloudDNS"
)

type DNSProviderConfig struct {
	Provider     DNSProvider        `json:"provider"`
	AwsRoute53   AwsRoute53Config   `json:"awsRoute53"`
	GcpCloudDNS  GcpCloudDNSConfig  `json:"gcpCloudDNS"`
	AzureDNSZone AzureDNSZoneConfig `json:"azureDNSZone"`
	AliCloudDNS  AliCloudDNSConfig  `json:"aliCloudDNS"`
}

type MeteringStorageProvider string

const (
	AwsS3MeteringStorageProvider       MeteringStorageProvider = "awsS3"
	AzureBlobMeteringStorageProvider   MeteringStorageProvider = "azureBlob"
	AliCloudOSSMeteringStorageProvider MeteringStorageProvider = "aliCloudOSS"
)

type MeteringStorageProviderConfig struct {
	// Provider selects which cloud-specific metering auth config is active.
	// Example:
	// meteringStorageProviderConfig:
	//   provider: azureBlob
	//   azureBlob:
	//     scope: /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>/blobServices/default/containers/<container>
	Provider    MeteringStorageProvider          `json:"provider"`
	AwsS3       AwsS3MeteringStorageConfig       `json:"awsS3"`
	AzureBlob   AzureBlobMeteringStorageConfig   `json:"azureBlob"`
	AliCloudOSS AliCloudOSSMeteringStorageConfig `json:"aliCloudOSS"`
}

type AwsS3MeteringStorageConfig struct {
	// These fields are reserved for future fine-grained metering access control.
	// Today AWS metering uses wildcard trust-policy handling instead of this config.
	AssumeRoleARN string `json:"assumeRoleARN"`
	ExternalID    string `json:"externalID,omitempty"`
}

type AzureBlobMeteringStorageConfig struct {
	// Scope is the Azure ARM resource ID that receives metering blob RBAC.
	// Use the narrowest scope possible, preferably a blob container resource ID.
	Scope string `json:"scope"`
}

type AliCloudOSSMeteringStorageConfig struct {
	// This field is reserved for future fine-grained metering access control.
	// Today AliCloud metering uses wildcard trust-policy handling instead of this config.
	AssumeRoleARN string `json:"assumeRoleARN"`
}

type AwsRoute53Config struct {
	AccountID  string `json:"accountID"`
	RoleArn    string `json:"roleArn"`
	ExternalId string `json:"externalId"`
	ZoneId     string `json:"zoneId"`
	Domain     string `json:"domain"`
}
type GcpCloudDNSConfig struct {
	ZoneId string `json:"zoneId"`
	Domain string `json:"domain"`
}

type AzureDNSZoneConfig struct {
	Domain         string `json:"domain"`
	ResourceGroup  string `json:"resourceGroup"`
	SubscriptionID string `json:"subscriptionID"`
	TenantID       string `json:"tenantID"`
	ClientID       string `json:"clientID"`
}

func (c AzureDNSZoneConfig) IsConfigured() bool {
	return strings.TrimSpace(c.Domain) != "" &&
		strings.TrimSpace(c.ResourceGroup) != "" &&
		strings.TrimSpace(c.SubscriptionID) != "" &&
		strings.TrimSpace(c.TenantID) != "" &&
		strings.TrimSpace(c.ClientID) != ""
}

type AliCloudDNSConfig struct {
	AccountID       string `json:"accountID"`
	SecretName      string `json:"secretName"`
	SecretNamespace string `json:"secretNamespace"`
	Domain          string `json:"domain"`
}
