package aks

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	corev1 "k8s.io/api/core/v1"
	storagev1 "k8s.io/api/storage/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"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/controllers/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

const (
	aksProvisioner = "disk.csi.azure.com"
	aksSkuName     = "PremiumV2_LRS"
)

type storageClass struct {
	*task.BaseTask
}

func NewStorageClass(ctx *task.Context) deploy.Task {
	return &storageClass{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func (t *storageClass) Name() string {
	return "StorageClass"
}

func (t *storageClass) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())

	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(fmt.Errorf("failed to get remote client: %w", err))
	}

	tags := controllers.ConvertToCloudTags(t.APPBox.Labels, consts.ProviderTypeAKS)
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)

	keys := make([]string, 0, len(tags))
	for key := range tags {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	tagPairs := make([]string, 0, len(keys))
	for _, key := range keys {
		val := tags[key]
		tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", controllers.ToAzureTagKey(key), val))
	}

	parameters := map[string]string{
		"csi.storage.k8s.io/fstype": "ext4",
		"skuName":                   aksSkuName,
		"cachingMode":               "None",
	}
	if len(tagPairs) > 0 {
		parameters["tags"] = strings.Join(tagPairs, ",")
	}

	sc := storagev1.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: consts.CloudDiskStorageClassName,
		},
		MountOptions:         []string{"nodelalloc,noatime"},
		Parameters:           parameters,
		Provisioner:          aksProvisioner,
		AllowVolumeExpansion: util.Pointer(true),
		ReclaimPolicy:        util.Pointer(corev1.PersistentVolumeReclaimDelete),
		VolumeBindingMode:    util.Pointer(storagev1.VolumeBindingWaitForFirstConsumer),
	}

	// Most fields of storageclass are immutable, so we use cli.Create instead of k8s.Apply
	if err := cli.Create(ctx, &sc); err != nil {
		if apierrors.IsAlreadyExists(err) {
			return deploy.Done()
		}
		return deploy.Error(fmt.Errorf("failed to create %s storage class", consts.CloudDiskStorageClassName))
	}
	return deploy.Done()
}

func (t *storageClass) PostCreate(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(fmt.Errorf("failed to get remote client: %w", err))
	}

	scName := consts.CloudDiskStorageClassName
	sc := storagev1.StorageClass{}
	if err := cli.Get(ctx, types.NamespacedName{Name: scName}, &sc); err != nil {
		return deploy.Error(fmt.Errorf("failed to get storage class %s: %w", scName, err))
	}
	return deploy.Done()
}

func (t *storageClass) Delete(ctx context.Context) deploy.Result {
	// When k8s cluster is deleted, the storage classes will be deleted automatically.
	// So currently we do not need to implement the delete logic for storage classes.
	return deploy.Done()
}

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