package task

import (
	"context"
	"fmt"
	"time"

	ackv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ack/v1alpha1"
	"github.com/go-logr/logr"
	eksv1beta1 "github.com/upbound/provider-aws/apis/eks/v1beta1"
	aksv1beta1 "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	cloudaws "github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/helm"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

const (
	Finalizer               = "appbox.dedicated.regionserver.tidbcloud.com/finalizer"
	ObservabilityNamespace  = "monitoring"
	CertManagerNamespace    = "cert-manager"
	ExternalDNSNamespace    = "external-dns"
	DedicatedAdminNamespace = "tidb-admin"
	// AnnotationKeyGatewayLBDeleted is used to mark the apigateway lb deletion completed
	// The apigatewayservice deletion task requires dataplane client, if the controller can't connect
	// to the dataplane k8s apiserver, then the deletion will be blocked. This may happen when the
	// permission is revoked in following deletion tasks. With annotation, we can skip this task deletion
	AnnotationKeyGatewayLBDeleted     = "tidbcloud.com/gateway-lb-deleted"
	AnnotationKeyGatewayVolumeDeleted = "tidbcloud.com/gateway-volume-deleted"
)

type Context struct {
	// Transient during a deployment
	Remote k8s.ClientGetter
	// Injected from the caller
	// Cluster is k8s cluster
	Cluster             ClusterIdentifier
	Local               client.Client
	Namer               *namer.Namer
	Logger              logr.Logger
	APPBox              *v1alpha1.APPBox
	VPC                 *v1alpha1.VPC
	Config              config.RootConfig
	InfraTmplConfig     *config.InfraTemplateConfig
	DedicatedTmplConfig *config.DedicatedTemplateConfig
	DPTmplConfig        *config.DPTemplateConfig
	AddonTmplConfig     *config.AddonTemplateConfig
	BeginAt             *time.Time
	AWSAMITypeGetter    cloudaws.AMITypeGetter
	APPBoxRW            repository.APPBoxReaderWriter
	VPCR                repository.VPCReader
	InternetR           repository.InternetReader
	LoadBalancerR       repository.LoadBalancerReader
	ChartApplier        helm.Applier

	AKSCluster *aksv1beta1.KubernetesCluster
}

var _ deploy.Task = &BaseTask{}

func (c *Context) Default() (*Context, error) {
	c.Remote = &k8s.EmptyGetter{}
	// Use vpc name and namespace as the eks & nodegroup name and namespace
	// to ensure compatibility
	c.Namer = namer.NewNamer().
		LegacyWithNetworkName(c.VPC.Name).
		LegacyWithNetworkNamespace(c.VPC.Namespace).
		WithAPPBoxName(c.APPBox.Name)
	c.ChartApplier = helm.ApplyFunc(helm.Apply)

	return c, nil
}

func (c *Context) ProviderConfigName() string {
	if c.VPC.Spec.AccountRef != nil {
		return c.VPC.Spec.AccountRef.ProviderConfigName
	}
	return ""
}

func (c *Context) RoleARN() string {
	if c.VPC.Spec.AccountRef == nil {
		return ""
	}
	switch c.VPC.Spec.Provider {
	case consts.ProviderTypeACK:
		return "acs:ram::" + c.VPC.Spec.AccountRef.AccountID + ":role/" + c.VPC.Spec.AccountRef.Role
	case consts.ProviderTypeEKS:
		return "arn:aws:iam::" + c.VPC.Spec.AccountRef.AccountID + ":role/" + c.VPC.Spec.AccountRef.Role
	}
	return ""
}

func (c *Context) K8sName() string {
	return controllers.UniqueIdentifier(c.APPBox.ObjectMeta, namer.WithPrefix(c.Config.ResourceNamePrefix))
}

func (c *Context) ProjectID() string {
	if c.VPC.Spec.AccountRef != nil {
		return c.VPC.Spec.AccountRef.AccountID
	}
	return ""
}

func (c *Context) AccountID() string {
	if c.VPC.Spec.AccountRef != nil {
		return c.VPC.Spec.AccountRef.AccountID
	}
	return ""
}

func (c *Context) IsBYOC() bool {
	return c.Config.BYOC.BYOCPrivateLink.IsEnabled()
}

// GetNodeZones returns effective AZs used by workload node resources (for example EKS worker nodegroups).
//
// Why this exists:
//   - Control-plane/network baseline AZs come from zones.
//   - Workload nodes may intentionally run in fewer AZs for cost/operational reasons
//     (for example, 1AZ/2AZ workload while control-plane keeps broader AZ coverage).
//
// Typical scenarios:
//   - Request-level restriction: CreateDataPlane(data_plane.node_zones) is provided.
//     It is persisted to DataPlane/VPC and should be honored by appbox tasks.
//   - Region default restriction: operators set regionConfig.nodeZones as a regional default.
//     It is resolved during dataplane creation and persisted to DataPlane/VPC.
//   - Legacy/unrestricted mode: no node-zone restriction configured; fall back to all zones.
//
// Priority:
//  1. VPC.Spec.NodeZones (request-level or persisted effective value)
//  2. VPC.Spec.Zones (baseline from VPC)
func (c *Context) GetNodeZones() []string {
	if c.VPC == nil {
		return nil
	}
	if len(c.VPC.Spec.NodeZones) > 0 {
		// Highest priority: explicit/persisted workload-node AZ restriction for this dataplane.
		return append([]string(nil), c.VPC.Spec.NodeZones...)
	}
	// Backward-compatible fallback when node-zone restriction is not configured.
	return append([]string(nil), c.VPC.Spec.Zones...)
}

func (c *Context) PCAIdentifierAndRegion() (identifier, region string, enabled bool, err error) {
	if c.APPBox != nil && c.APPBox.Spec.PCA != nil {
		identifier = c.APPBox.Spec.PCA.CAIdentifier
		region, err = util.ParseAWSPCAARN(identifier)
		if err != nil {
			return "", "", false, err
		}
		return identifier, region, true, nil
	}

	if c.Config.APPBoxConfig.PCAConfig == nil {
		return "", "", false, nil
	}

	return c.Config.APPBoxConfig.PCAConfig.CAIdentifier, c.Config.APPBoxConfig.PCAConfig.Region, true, nil
}

func (c *Context) PCAIssuerPolicyTemplateFile() string {
	if c.Config.APPBoxConfig.PCAConfig == nil {
		return ""
	}
	return c.Config.APPBoxConfig.PCAConfig.IssuerPolicyTemplateFile
}

func (c *Context) PCAAssumeRoleARN() string {
	if c.Config.APPBoxConfig.PCAConfig == nil {
		return ""
	}
	return c.Config.APPBoxConfig.PCAConfig.AssumeRoleArn
}

type BaseTask struct {
	*Context
}

func (t *BaseTask) Name() string {
	return ""
}

func (t *BaseTask) Category() deploy.TaskCategory {
	return deploy.ClusterTask
}

func (t *BaseTask) Create(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) MetricsStartTime() *time.Time {
	return t.BeginAt
}

func (t *BaseTask) GetDnsProviderConfigName() string {
	return fmt.Sprintf("dns-%s", t.Config.DNSProviderConfig.AwsRoute53.AccountID)
}

type ClusterIdentifier interface {
	GetNamespace() string
	GetName() string
	GetOIDCURL() string
}

type eksCluster struct {
	cluster *eksv1beta1.Cluster
}

func NewEKSClusterIdentifier(cluster *eksv1beta1.Cluster) ClusterIdentifier {
	return &eksCluster{cluster: cluster}
}

func (c *eksCluster) GetNamespace() string {
	return c.cluster.Namespace
}

func (c *eksCluster) GetName() string {
	return c.cluster.Name
}

func (c *eksCluster) GetOIDCURL() string {
	return ""
}

type ackCluster struct {
	cluster *ackv1alpha1.ManagedKubernetes
}

func NewACKClusterIdentifier(cluster *ackv1alpha1.ManagedKubernetes) ClusterIdentifier {
	return &ackCluster{cluster: cluster}
}

func (c *ackCluster) GetNamespace() string {
	return c.cluster.Namespace
}

func (c *ackCluster) GetName() string {
	return c.cluster.Name
}

func (c *ackCluster) GetOIDCURL() string {
	for _, data := range c.cluster.Status.AtProvider.RrsaMetadata {
		if data.Enabled != nil && *data.Enabled && data.RrsaOidcIssuerURL != nil {
			return *data.RrsaOidcIssuerURL
		}
	}
	return ""
}

type aksCluster struct {
	cluster *aksv1beta1.KubernetesCluster
}

func NewAKSClusterIdentifier(cluster *aksv1beta1.KubernetesCluster) ClusterIdentifier {
	return &aksCluster{cluster: cluster}
}

func (c *aksCluster) GetNamespace() string {
	return c.cluster.Namespace
}

func (c *aksCluster) GetName() string {
	return c.cluster.Name
}

func (c *aksCluster) GetOIDCURL() string {
	return ptr.Deref(c.cluster.Status.AtProvider.OidcIssuerURL, "")
}
