package ack

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 (
	provisioner             = "diskplugin.csi.alibabacloud.com"
	defaultPerformanceLevel = "PL1"
)

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.ProviderTypeACK)
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)

	kvs := make([]string, 0, len(tags))
	for k, v := range tags {
		kvs = append(kvs, fmt.Sprintf("%s:%s", k, v))
	}
	// sort tags by key to ensure consistent ordering
	sort.Strings(kvs)
	diskTags := strings.Join(kvs, ",")

	encrypted := "true"
	if t.Config.APPBoxConfig.CSIDriver.DisableEncryption {
		encrypted = "false"
	}
	sc := storagev1.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: consts.CloudDiskStorageClassName,
		},
		MountOptions: []string{"nodelalloc,noatime"},
		Parameters: map[string]string{
			"fstype":          "ext4",
			"type":            "cloud_auto",
			"encrypted":       encrypted,
			"diskTags":        diskTags,
			"burstingEnabled": "true",
		},
		Provisioner:          provisioner,
		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()
}
