package aks

import (
	"context"
	"fmt"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	xpcomputev2 "github.com/upbound/provider-azure/apis/compute/v1beta2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/controllers/common"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util/crossplane"
)

type DiskEncryptionSetTask struct {
	task.BaseTask
}

var _ deploy.Task = &DiskEncryptionSetTask{}

func NewDiskEncryptionSetTask(c *task.Context) *DiskEncryptionSetTask {
	return &DiskEncryptionSetTask{BaseTask: task.BaseTask{Context: c}}

}

func (t *DiskEncryptionSetTask) Name() string {
	return "DiskEncryptionSet"
}

func (t *DiskEncryptionSetTask) Create(sctx context.Context) deploy.Result {
	if !t.Cluster.FeatureEnabled(consts.ClusterFeatureUseCMEK) {
		return deploy.Done()
	}

	cmek := t.AKSClusterSpec.AzureCMEK
	des := &xpcomputev2.DiskEncryptionSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getDiskEncryptionSetName(),
		},
		Spec: xpcomputev2.DiskEncryptionSetSpec{
			ForProvider: xpcomputev2.DiskEncryptionSetParameters{
				Location:          ptr.To(t.Cluster.GetRegion()),
				ResourceGroupName: &t.Cluster.Spec.Kubernetes.Cloud.Azure.NodeResourceGroup,
				KeyVaultKeyID:     ptr.To(cmek.KeyURI()),
				Identity: &xpcomputev2.IdentityParameters{
					Type:        ptr.To("UserAssigned"),
					IdentityIds: []*string{&cmek.ManagedIdentityResourceID},
				},
				// AutoKeyRotationEnabled: ptr.To(true),
				EncryptionType:    ptr.To("EncryptionAtRestWithCustomerKey"),
				FederatedClientID: &cmek.ApplicationID,
				Tags:              t.resourceLabels(),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.GetAKSDataplaneProviderConfigName(),
				},
			},
		},
	}
	err := k8s.Apply(sctx, t.Local, des)
	if err != nil {
		return deploy.Error(fmt.Errorf("apply disk encryption set: %w", err))
	}
	return deploy.Done()
}

// PostCreate check whether the disk encryption set is ready and synced.
// If true, save the disk encryption set ID to the cluster status.
func (t *DiskEncryptionSetTask) PostCreate(ctx context.Context) deploy.Result {
	if !t.Cluster.FeatureEnabled(consts.ClusterFeatureUseCMEK) {
		return deploy.Done()
	}
	logger := log.FromContext(ctx)

	key := t.getDiskEncryptionSetName()
	des := &xpcomputev2.DiskEncryptionSet{}
	if err := t.Local.Get(ctx, client.ObjectKey{Name: key}, des); err != nil {
		return deploy.Error(fmt.Errorf("get disk encryption set %s: %w", key, err))
	}
	if !crossplane.IsReadyAndSynced(des.Status.Conditions) {
		logger.Info("disk encryption set is not ready and synced", "name", key, "conditions", des.Status.Conditions)
		return deploy.Error(fmt.Errorf("disk encryption set %s is not ready and synced", key))
	}

	if t.Cluster.Status.Azure == nil {
		t.Cluster.Status.Azure = &cloudv1.AzureClusterStatus{}
	}
	t.Cluster.Status.Azure.DiskEncryptionSetResourceID = ptr.Deref(des.Status.AtProvider.ID, "")

	return deploy.Done()
}

func (t *DiskEncryptionSetTask) Delete(sctx context.Context) deploy.Result {
	if !t.Cluster.FeatureEnabled(consts.ClusterFeatureUseCMEK) {
		return deploy.Done()
	}

	des := &xpcomputev2.DiskEncryptionSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getDiskEncryptionSetName(),
		},
	}

	if err := k8s.DeleteObjectCompleted(sctx, t.Local, des); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *DiskEncryptionSetTask) getDiskEncryptionSetName() string {
	return fmt.Sprintf("des-%s", t.Cluster.Name)
}

func (t *DiskEncryptionSetTask) resourceLabels() map[string]*string {
	l := map[string]string{}

	// Append billing labels
	l[cloudv1.TagLabelPrefix+common.UsedBy] = common.CustomerTiDB
	l[cloudv1.TagLabelPrefix+common.Cluster] = t.Cluster.Name
	l[cloudv1.TagLabelPrefix+common.ServiceType] = common.Dedicated
	l[cloudv1.TagLabelPrefix+common.Project] = t.Cluster.GetProjectID()
	l[cloudv1.TagLabelPrefix+common.Tenant] = t.Cluster.GetTenantID()
	if env, ok := t.Cluster.Labels[cloudv1.TagLabelPrefix+common.Environment]; ok {
		l[cloudv1.TagLabelPrefix+common.Environment] = env
	}

	// Strip from tag prefix
	return convertMapstringToPointerMapString(cloudv1.TagsFromLabels(l))
}
