package task

import (
	"context"
	"time"

	eksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	gkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	"github.com/go-logr/logr"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/azure"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/appbox"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/internet"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	agentcontrolapi "github.com/tidbcloud/observability/agent-control-server/pkg/agentcontrol/api"
	"github.com/upbound/provider-azure/apis/containerservice/v1beta1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	Finalizer               = "appbox.dedicated.regionserver.tidbcloud.com/finalizer"
	ObservabilityNamespace  = "monitoring"
	CertManagerNamespace    = "cert-manager"
	DedicatedAdminNamespace = "tidb-admin"
	GKEAdminMaxPodsPerNode  = 32
	GKEAdminNodeDiskSizeGB  = 100
	GKENameLimit            = 40
)

type Context struct {
	// Transient during a deployment
	Remote k8s.ClientGetter
	// Injected from the caller
	Cluster             ClusterIdentifier
	AKSCluster          *v1beta1.KubernetesCluster
	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
	APPBoxRW            appbox.APPBoxReaderWriter
	VPCR                vpc.VPCReader
	InternetR           internet.InternetReader
	ChartApplier        helm.Applier
	AgentControlAPI     agentcontrolapi.AgentControlAPI

	// Provider specific
	AKSCli azure.AKSClient
}

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)

	switch c.Config.Provider {
	case consts.ProviderTypeAKS:
		// Use the provider config name as the subscription ID
		// aksCli is related to the subscription ID, so we need to create a new one for each APPBox
		subscriptionID := c.ProviderConfigName()
		aksCli, err := azure.NewAKSClient(subscriptionID)
		if err != nil {
			return nil, err
		}
		c.AKSCli = aksCli
	default:
	}
	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 "arn:aws:iam::" + c.VPC.Spec.AccountRef.AccountID + ":role/" + c.VPC.Spec.AccountRef.Role
	}
	return ""
}

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

func (c *Context) GKEName() string {
	// we need to overwrite some names for migrated appbox
	if name := c.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameGKE]; name != "" {
		return name
	}
	return namer.UniqueIdentifier(c.APPBox.ObjectMeta, namer.WithLimit(GKENameLimit))
}

type BaseTask struct {
	*Context
}

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

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

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
}

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

type GKEClusterIdentifier interface {
	ClusterIdentifier
	GetLocations() []string
}

type eksCluster struct {
	cluster *eksv1beta1.Cluster
}

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

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

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

type gkeCluster struct {
	cluster *gkev1beta2.Cluster
}

func NewGKEClusterIdentifier(cluster *gkev1beta2.Cluster) *gkeCluster {
	return &gkeCluster{cluster: cluster}
}

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

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

func (c *gkeCluster) GetLocations() []string {
	return append([]string(nil), c.cluster.Spec.ForProvider.Locations...)
}

type aksCluster struct {
	cluster *v1beta1.KubernetesCluster
}

func NewAKSClusterIdentifier(cluster *v1beta1.KubernetesCluster) *aksCluster {
	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) GetOIDCissuerURL() string {
	return *c.cluster.Status.AtProvider.OidcIssuerURL
}
