package utils

import (
	"fmt"

	corev1 "k8s.io/api/core/v1"
	storagev1 "k8s.io/api/storage/v1"
	storagev1beta1 "k8s.io/api/storage/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"

	"github.com/tidbcloud/infra-provider/pkg/consts"
	pkgutil "github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	ebsProvisioner   = "ebs.csi.aws.com"
	pdProvisioner    = "pd.csi.storage.gke.io"
	azureProvisioner = "disk.csi.azure.com"
)

var (
	bindingMode               = storagev1.VolumeBindingWaitForFirstConsumer
	reclaimPolicy             = corev1.PersistentVolumeReclaimDelete
	adjustableIOPSVolumeTypes = map[string]bool{
		consts.AWSVolumeTypeGP2:               false,
		consts.AWSVolumeTypeGP3:               true,
		consts.AWSVolumeTypeIO1:               true,
		consts.AWSVolumeTypeIO2:               true,
		consts.GCPVolumeTypePDSSD:             false,
		consts.AZUREVolumeTypePremiumV2:       true,
		consts.AZUREVolumeTypeUltraSSD:        true,
		consts.GCPVolumeTypeHyperdiskBalanced: true,
	}
	adjustableThroughputVolumeTypes = map[string]bool{
		consts.AWSVolumeTypeGP2:               false,
		consts.AWSVolumeTypeGP3:               true,
		consts.AWSVolumeTypeIO1:               false,
		consts.AWSVolumeTypeIO2:               false,
		consts.GCPVolumeTypePDSSD:             false,
		consts.AZUREVolumeTypePremiumV2:       true,
		consts.AZUREVolumeTypeUltraSSD:        true,
		consts.GCPVolumeTypeHyperdiskBalanced: true,
	}

	// supportedParameters is a map of provisioner to supported parameters for VolumeAttributesClass.
	supportedParameters = map[string]map[string]struct{}{
		// Refer to: https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/f3adda74adbaee34199478c306e6961c837d9df1/pkg/driver/controller_modify_volume.go#L176-L215
		ebsProvisioner: {
			"iops":       {},
			"throughput": {},
			"type":       {},
		},
	}
)

func GetVolumeAttributesClassFromFromStorageClass(sc *storagev1.StorageClass) *storagev1beta1.VolumeAttributesClass {
	vac := &storagev1beta1.VolumeAttributesClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: sc.Name,
		},
		DriverName: sc.Provisioner,
		Parameters: map[string]string{},
	}

	if supportedParams, ok := supportedParameters[sc.Provisioner]; ok {
		for k, v := range sc.Parameters {
			if _, ok2 := supportedParams[k]; ok2 {
				vac.Parameters[k] = v
			}
		}
	}

	// Must have at least one parameter
	if len(vac.Parameters) > 0 {
		return vac
	}
	return nil
}

// Currently, we have the storageclass name convention between core-svc and infra-provider: {clusterComponent}-{volumeType}-{iops}-{throughput}, eg. "tikv-gp3-3000-144"
// For volume type that does not support customizing iops and throughput, the {iops} and {throughput} are omitted in the storageclass name, such as gp2 and pd-ssd
// For unsupported storageclass name, such as tidb `index-acceleration`, we'll get the parameters directly from configuration file.
// For cmek storageclass (tikv-gp3-3000-144-aws_account-keyid), it's created automatically mirroring normal storageclass.
// In the future, tidb-mgmt will pass the volume parameters struct directly
func ParseStorageClassFromName(name string) (*storagev1.StorageClass, error) {
	sc := &storagev1.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		VolumeBindingMode:    &bindingMode,
		AllowVolumeExpansion: pointer.Bool(true),
		ReclaimPolicy:        &reclaimPolicy,
		Parameters: map[string]string{
			"csi.storage.k8s.io/fstype": "ext4",
		},
		MountOptions: []string{"nodelalloc,noatime"},
	}

	volumeType, iops, throughput := pkgutil.GetTypeIOPSThroughputFromClassName(name)
	if volumeType == consts.GCPVolumeTypePDSSD { // tikv-pd-ssd, gcp pd-ssd disk
		sc.Provisioner = pdProvisioner
		sc.Parameters["type"] = consts.GCPVolumeTypePDSSD
		return sc, nil
	}
	if volumeType == consts.AZUREVolumeTypeStandard_LRS_ALIAS {
		sc.Parameters["skuname"] = consts.AZUREVolumeTypeStandard_LRS
		sc.Provisioner = azureProvisioner
		return sc, nil
	}
	if volumeType == consts.AWSVolumeTypeGP2 { // tiflash-gp2
		sc.Parameters["type"] = consts.AWSVolumeTypeGP2
		sc.Parameters["encrypted"] = "true"
		sc.Provisioner = ebsProvisioner
		return sc, nil
	}

	if volumeType == consts.AZUREVolumeTypePremiumV2 {
		// Azure:
		// pd-premiumv2-3000-400
		// tikv-premiumv2-3000-400
		sc.Provisioner = azureProvisioner
		sc.Parameters["DiskIOPSReadWrite"] = fmt.Sprint(iops)
		sc.Parameters["DiskMBpsReadWrite"] = fmt.Sprint(throughput)
		sc.Parameters["skuName"] = "PremiumV2_LRS"
	}
	if volumeType == consts.AZUREVolumeTypeUltraSSD {
		// Azure:
		// pd-ultrassd-3000-400
		// tikv-ultrassd-3000-400
		// https://learn.microsoft.com/en-us/azure/aks/use-ultra-disks#create-the-storage-class
		sc.Provisioner = azureProvisioner
		sc.Parameters["DiskIOPSReadWrite"] = fmt.Sprint(iops)
		sc.Parameters["DiskMBpsReadWrite"] = fmt.Sprint(throughput)
		sc.Parameters["skuName"] = "UltraSSD_LRS"
		sc.Parameters["cachingMode"] = "None"
		sc.Parameters["kind"] = "managed"
	}
	if volumeType == consts.AWSVolumeTypeGP3 || volumeType == consts.AWSVolumeTypeIO1 || volumeType == consts.AWSVolumeTypeIO2 {
		// tikv-gp3-3000-400
		// tikv-io1-3000-400
		// tikv-io2-3000-400
		sc.Parameters["type"] = volumeType
		sc.Provisioner = ebsProvisioner
		// Pass the tags to the persistent volumes
		// See https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/TAGGING.md for more information
		sc.Parameters["tagSpecification"] = fmt.Sprintf("%s=%s", consts.TagKeyCrossplaneVendor, consts.TagValCrossplaneVendor)
		sc.Parameters["encrypted"] = "true"
		sc.Parameters["iops"] = fmt.Sprint(iops)
		if adjustableThroughputVolumeTypes[volumeType] {
			sc.Parameters["throughput"] = fmt.Sprint(throughput)
		}
	}
	if volumeType == consts.GCPVolumeTypeHyperdiskBalanced {
		// tikv-hyperdiskbalanced-300-400
		// See https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/hyperdisk#hyperdisk-balanced
		sc.Provisioner = pdProvisioner
		sc.Parameters["type"] = consts.GCPVolumeTypeHyperdiskBalanced
		sc.Parameters["provisioned-throughput-on-create"] = fmt.Sprintf("%dMi", throughput)
		sc.Parameters["provisioned-iops-on-create"] = fmt.Sprint(iops)
	}

	if volumeType != "" {
		// these have already been checked in GetTypeIOPSThroughputFromClassName
		if iops != 0 && !adjustableIOPSVolumeTypes[volumeType] {
			return nil, fmt.Errorf("uncustomizable iops volume type %s", name)
		}
		if throughput != 0 && !adjustableThroughputVolumeTypes[volumeType] {
			return nil, fmt.Errorf("uncustomizable throughput volume type %s", name)
		}
	} else {
		return nil, fmt.Errorf("unsupported storageclass name %s", name)
	}

	return sc, nil
}
