package aks

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	azurestoragev1beta1 "github.com/upbound/provider-azure/apis/storage/v1beta1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/controllers/dm/task"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
)

type StorageAccount struct {
	*task.BaseTask
}

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

func NewStorageAccount(ctx *task.Context) *StorageAccount {
	return &StorageAccount{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func GetStorageAccountName(projectId, region, clusterName string) string {
	// 3-24 characters, lowercase letters and numbers
	originalString := fmt.Sprintf("dm-%s-%s-%s", projectId, region, clusterName)
	hash := md5.Sum([]byte(originalString))
	hashString := hex.EncodeToString(hash[:])
	result := fmt.Sprintf("dm%s", hashString[:22])
	return result
}

func (t *StorageAccount) Create(ctx context.Context) deploy.Result {
	name := GetStorageAccountName(t.ProjectID, t.GetRegion(), t.GetClusterScopeName())

	resourceGroupName := t.Config.DMConfig.Azure.ControlPlaneResourceGroup

	storageAccount := &azurestoragev1beta1.Account{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: t.GetCommonLabels4Azure(),
		},
		Spec: azurestoragev1beta1.AccountSpec{
			ForProvider: azurestoragev1beta1.AccountParameters{
				AccessTier:             ptr.To("Hot"),
				AccountKind:            ptr.To("StorageV2"),
				AccountReplicationType: ptr.To("LRS"),
				AccountTier:            ptr.To("Standard"),
				IsHnsEnabled:           ptr.To(true),
				Location:               ptr.To(t.GetRegion()),
				ResourceGroupName:      &resourceGroupName,
				ResourceGroupNameRef: &xpv1.Reference{
					Name: resourceGroupName,
				},
				Tags: t.GetResourceTag(),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.GetProviderConfigName(),
				},
			},
		},
	}

	client := t.Local

	if err := client.Create(ctx, storageAccount); err != nil {
		// if already exists, do nothing
		if apierrors.IsAlreadyExists(err) {
			log := t.Context.Logger
			log.Info("storage account already exists", "name", name)
			return deploy.Done()
		}

		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *StorageAccount) PostCreate(ctx context.Context) deploy.Result {
	storageAccountName := GetStorageAccountName(t.ProjectID, t.GetRegion(), t.GetClusterScopeName())
	storageAccount := &azurestoragev1beta1.Account{
		ObjectMeta: metav1.ObjectMeta{Name: storageAccountName},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(storageAccount), storageAccount); err != nil {
		if apierrors.IsNotFound(err) {
			// resource not ready, retry
			t.Logger.Info("storage account not found, will retry", "name", storageAccountName)
			return deploy.Error(err).WithRequeuAfter(time.Second * 10)
		}
		t.Logger.Info("get storage account error", "error", err)
		return deploy.Error(err)
	}

	var isReady bool
	for _, condition := range storageAccount.Status.Conditions {
		if condition.Type == xpv1.TypeReady && condition.Status == corev1.ConditionTrue {
			isReady = true
			break
		}
	}
	if !isReady {
		t.Logger.Info("storage account is not ready")
		return deploy.Error(errors.New("storage account is not ready"))
	}

	subscriptionID := t.Config.DMConfig.Azure.CrossplaneAzureProviderSubscriptionID
	t.Logger.Info("subscriptionID", "subscriptionID", subscriptionID)

	aksClient, err := NewAKSClient(subscriptionID)
	if err != nil {
		t.Logger.Info("create aks client error", "error", err)
		return deploy.Error(err)
	}

	resourceGroupName := t.Config.DMConfig.Azure.ControlPlaneResourceGroup
	accountKey, err := aksClient.GetStorageAccountKey(ctx, resourceGroupName, storageAccountName)
	if err != nil {
		t.Logger.Error(err, "failed to get storage account key")
		return deploy.Error(err)
	}

	t.DMJob.Status.StorageAccountKey = accountKey
	t.DMJob.Status.StorageAccountName = storageAccountName

	return deploy.Done()
}
