package eks

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

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

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"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/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

const (
	ebsProvisioner = "ebs.csi.aws.com"
)

type StorageClass struct {
	*task.BaseTask
}

func NewStorageClass(ctx *task.Context) *StorageClass {
	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, cloudv1.ProviderTypeEKS)
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)
	// sort tags by key to ensure consistent ordering
	keys := make([]string, 0, len(tags))
	for key := range tags {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	params := map[string]string{
		"csi.storage.k8s.io/fstype": "ext4",
		"type":                      "gp3",
		"encrypted":                 "true",
	}
	// https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/tagging.md
	count := 1
	for _, key := range keys {
		val := tags[key]
		tagKey := fmt.Sprintf("tagSpecification_%d", count)
		params[tagKey] = fmt.Sprintf("%s=%s", key, val)
		count++
	}
	sc := storage.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: consts.CloudDiskStorageClassName,
		},
		MountOptions:         []string{"nodelalloc,noatime"},
		Parameters:           params,
		Provisioner:          ebsProvisioner,
		AllowVolumeExpansion: util.Pointer(true),
		ReclaimPolicy:        util.Pointer(corev1.PersistentVolumeReclaimDelete),
		VolumeBindingMode:    util.Pointer(storage.VolumeBindingWaitForFirstConsumer),
	}

	if err := k8s.Apply(ctx, cli, &sc); err != nil {
		return deploy.Error(fmt.Errorf("failed to apply %s storage class: %w", consts.CloudDiskStorageClassName, err))
	}
	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 := storage.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()
}
