package eks

import (
	"context"
	"fmt"

	"github.com/aws/aws-sdk-go-v2/service/eks"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/eks-provider/controllers/cluster/task/utils/annotator"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

// Guardian adds and removes the finalizer of the Cluster.
type Guardian struct {
	*task.BaseTask

	AWSCli aws.Client
}

func NewGuardian(ctx *task.Context, awsCli aws.Client) *Guardian {
	return &Guardian{BaseTask: &task.BaseTask{Context: ctx}, AWSCli: awsCli}
}

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

func (t *Guardian) Create(ctx context.Context) deploy.Result {
	// Set cmek annotation
	// this annotation is used in the cluster server
	if t.Cluster.FeatureEnabled(consts.ClusterFeatureUseCMEK) {
		if kmsARN := t.KMSGetter.Get(); kmsARN != "" {
			cloudv1.SetEKSCMEKAnnotations(&t.Cluster.ObjectMeta, kmsARN)
		}
	}
	shouldStop := false
	// prevent reconciling objects from different regions
	if t.EnvProvider.GetRegion() != t.Config.Network.Region {
		return deploy.Panic(nil)
	}
	// ignore resource that doesn't belong to this region
	if !util.ContainsRegionLabel(t.Cluster) {
		if t.LegacyNetwork != nil {
			if ok, err := util.CheckNetworkRegion(t.Config.Network.Region, t.LegacyNetwork); err != nil {
				return deploy.Panic(err)
			} else if !ok {
				return deploy.Panic(nil)
			}
		}
		// ensure the object contains the region label
		if !util.EnsureRegion(t.Cluster, t.EnvProvider.GetRegion()) {
			shouldStop = true
		}
	}

	annotator.AnnotateCluster(t.Config.Override, t.Cluster)
	if !controllerutil.ContainsFinalizer(t.Cluster, task.Finalizer) {
		controllerutil.AddFinalizer(t.Cluster, task.Finalizer)
		// Require immediate requeue without running other tasks
		shouldStop = true
	}
	if t.Cluster.Status.Phase == "" {
		t.Cluster.Status.Phase = cloudv1.ClusterPhasePending
	}
	switch phase := t.Cluster.Status.Phase; phase {
	case cloudv1.ClusterPhasePending:
		shouldStop = true

		err := t.readyToCreate(ctx)
		if err != nil {
			return deploy.Panic(fmt.Errorf("cluster is not ready to create: %v", err))
		}
		// all new clusters will auto set node version defaultly
		abort, err := t.setNodeVersion(ctx, false)
		if err != nil {
			return deploy.Panic(fmt.Errorf("failed set node version: %w", err))
		}
		// make sure that node version is set in pending phase
		if abort {
			break
		}

		abort, err = t.enableSelfManagedNodeGroup()
		if err != nil {
			return deploy.Panic(fmt.Errorf("failed check self managed nodegroup feature: %w", err))
		}

		if abort {
			break
		}

		t.Cluster.Status.Phase = cloudv1.ClusterPhaseCreating

	case cloudv1.ClusterPhaseCreating, cloudv1.ClusterPhaseRunning,
		cloudv1.ClusterPhaseUnavailable, cloudv1.ClusterPhaseScaling,
		cloudv1.ClusterPhaseUpgrading, cloudv1.ClusterPhaseSuspend:
		abort, err := t.setNodeVersion(ctx, true)
		if err != nil {
			return deploy.Panic(fmt.Errorf("failed set node version in annotation: %w", err))
		}
		// make sure that node version is set in pending phase
		if abort {
			shouldStop = true
			break
		}

		if err := t.setAccount(ctx); err != nil {
			return deploy.Panic(err)
		}
	default:
		return deploy.Panic(fmt.Errorf("unexpected cluster phase during creation: %s", phase))
	}

	if shouldStop {
		return deploy.Panic(nil).WithRequeuAfter(0)
	}

	return deploy.Done()
}

func (t *Guardian) readyToCreate(ctx context.Context) error {
	if t.LegacyNetwork != nil {
		if phase := t.LegacyNetwork.Status.Phase; phase != cloudv1.NetworkPhaseNormal {
			return fmt.Errorf("network phase not ready: %s", phase)
		}
	}
	return nil
}

func (t *Guardian) Delete(ctx context.Context) deploy.Result {
	switch phase := t.Cluster.Status.Phase; phase {
	case cloudv1.ClusterPhaseCreating, cloudv1.ClusterPhaseRunning, "":
		t.Cluster.Status.Phase = cloudv1.ClusterPhasePendingDelete
		// Require immediate requeue without running other tasks
		return deploy.Panic(nil).WithRequeuAfter(0)
	case cloudv1.ClusterPhasePendingDelete:
		err := t.readyToDelete(ctx)
		if err != nil {
			return deploy.Panic(fmt.Errorf("cluster is not ready to delete: %v", err))
		}
		t.Cluster.Status.Phase = cloudv1.ClusterPhaseDeleting
	case cloudv1.ClusterPhaseDeleting:
		err := t.deleteAccount(ctx)
		if err != nil {
			return deploy.Error(err)
		}
	default:
		return deploy.Panic(fmt.Errorf("unexpected cluster phase during deletion: %s", phase))
	}
	return deploy.Done()
}

func (t *Guardian) readyToDelete(ctx context.Context) error {
	return nil
}

func (t *Guardian) deleteAccount(ctx context.Context) error {
	return deleteEncrptionKey(ctx, t.Local, t.Cluster)
}

func (t *Guardian) setAccount(ctx context.Context) error {
	kmsARN := t.KMSGetter.Get()
	if kmsARN == "" {
		// try to new default kms key
		arn, err := ensureEncryptionKey(ctx, t.Local, t.EnvProvider.GetRegion(), t.Cluster, t.GetAWSProviderConfigName())
		if err != nil {
			return err
		}

		kmsARN = arn
	}

	if t.EKSClusterSpec.Security == nil {
		t.EKSClusterSpec.Security = &cloudv1.Security{}
	}
	t.EKSClusterSpec.Security.MasterKeyARN = &kmsARN

	t.EKSClusterSpec.ComponentPCAARN = pointer.String(t.AcctInfoGetter.GetComponentPCAARN())

	if t.LegacyNetwork != nil {
		networkStatus, err := eksv1alpha1.EKSNetworkStatusFromNetwork(t.LegacyNetwork)
		if err != nil {
			return err
		}

		t.EKSClusterSpec.BackupStorage = cloudv1.StorageInfo{
			StorageType: cloudv1.StorageTypeS3,
			Region:      t.EnvProvider.GetRegion(),
			Bucket:      networkStatus.BucketName,
		}
	}

	return nil
}

func (t *Guardian) setNodeVersion(ctx context.Context, anno bool) (abort bool, _ error) {
	// node version has been set, just return
	if t.CommonK8sClusterStatus.NodeVersion != "" {
		return false, nil
	}

	// control plane version has been set in anno, just return
	if _, ok := t.Cluster.Annotations[consts.AnnoKeyControlPlaneVersion]; ok && anno {
		return false, nil
	}

	c := t.AWSCli.EKS(t.GetAWSAssumedRoleForSDK())
	name := t.EnvProvider.GetClusterName()
	out, err := c.DescribeCluster(ctx, &eks.DescribeClusterInput{
		Name: &name,
	})
	if err != nil {
		return true, err
	}

	if out.Cluster == nil || out.Cluster.Version == nil {
		return true, fmt.Errorf("version of eks is not found")
	}

	version := *out.Cluster.Version
	if anno {
		t.Cluster.Annotations[consts.AnnoKeyControlPlaneVersion] = version
	} else {
		t.CommonK8sClusterStatus.NodeVersion = version
	}

	return true, nil
}

func (t *Guardian) enableSelfManagedNodeGroup() (abort bool, _ error) {
	if util.GetAnnotation(t.Cluster, consts.UseSelfManagedNodegroupAnnotation) == consts.AnnoValTrue {
		return false, nil
	}

	//  2. the AMI ID must be populated when starting the infra-provider
	if len(t.Config.AMIConfig.ARMAMIIDs) == 0 || len(t.Config.AMIConfig.X86AMIIDs) == 0 {
		return true, fmt.Errorf("ami config is not populated")
	}

	nodeVersion := t.GetNodeVersion(false)

	//  3. the current k8s control plane version's AMI ID exists
	_, ok1 := t.Config.AMIConfig.ARMAMIIDs[nodeVersion]
	_, ok2 := t.Config.AMIConfig.X86AMIIDs[nodeVersion]
	if !ok1 || !ok2 {
		return true, fmt.Errorf("ami id for %s is not found", nodeVersion)
	}

	util.SetAnnotation(t.Cluster, consts.UseSelfManagedNodegroupAnnotation, consts.AnnoValTrue)

	return true, nil
}
