package action

import (
	"fmt"

	csngapiv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/pkg/dataplane"
	"github.com/tidbcloud/cluster-service-ng/pkg/k8s"
	storagev1 "github.com/tidbcloud/cluster-service-ng/pkg/k8s/api/storage/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	storagev1beta1 "k8s.io/api/storage/v1beta1"
)

const (
	awsDriverName      = "ebs.csi.aws.com"
	alicloudDriverName = "diskplugin.csi.alibabacloud.com"
	azureDriverName    = "disk.csi.azure.com"
)

type CreateVolumeAttributesClassAction struct {
	dataplaneManager dataplane.Manager
	sharedPoolRepo   repo.SharedPoolRepo
}

const CreateVolumeAttributesClassActionName = "create-volume-attribute-class"

func NewCreateVolumeAttributesClassAction(
	sharedPoolRepo repo.SharedPoolRepo,
	dataplaneManager dataplane.Manager,
) actions.Action {
	return &CreateVolumeAttributesClassAction{
		sharedPoolRepo:   sharedPoolRepo,
		dataplaneManager: dataplaneManager,
	}
}

func (a *CreateVolumeAttributesClassAction) Name() string {
	return CreateVolumeAttributesClassActionName
}

// Invoke creates vac for the shared pool in dataplane.
func (a *CreateVolumeAttributesClassAction) Invoke(args actions.ActionArgs) error {
	// generate context
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "create placeholder deployment action invoked")

	sharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		return fmt.Errorf("failed to get sharedPool id: %w", err)
	}

	// load sharedPool and its content
	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolId, false)
	if err != nil {
		return fmt.Errorf("failed to load sharedPool: %w", err)
	}

	sharedPoolStr, err := args.GetString(workflow.SharedPoolKey)
	if err != nil {
		return fmt.Errorf("failed to get sharedPool: %w", err)
	}

	var tempSharedPool entity.SharedPool
	err = protojson.Unmarshal([]byte(sharedPoolStr), &tempSharedPool)
	if err != nil {
		tilog.Error(ctx, "failed to unmarshal sharedPool", zap.Error(err))
		return fmt.Errorf("failed to unmarshal sharedPool: %w", err)
	}

	sharedPool.ComponentGroups = tempSharedPool.GetComponentGroups()

	// skip vac on alicloud
	// all component use vac to set volume.
	storages := make(map[string]*entity.Storage)
	for _, comp := range sharedPool.GetComponentGroups() {
		for _, storage := range comp.GetStorages() {
			if storage == nil {
				continue
			}
			if storage.GetType() == entity.LocalSSD {
				continue
			}
			key := workflow.GenVolumeAttributesClassName(storage)
			storages[key] = storage
		}
	}

	remoteCli, err := a.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for shared pool", zap.String("dataplaneName", sharedPool.GetDataplaneName()), zap.Error(err))
		return fmt.Errorf("failed to get remote client for shared pool: %w", err)
	}

	var driverName string
	switch sharedPool.GetProvider().GetType() {
	case csngapiv1beta.ProviderType_ALICLOUD:
		driverName = alicloudDriverName
	case csngapiv1beta.ProviderType_AWS:
		driverName = awsDriverName
	case csngapiv1beta.ProviderType_AZURE:
		driverName = azureDriverName
	default:
		return fmt.Errorf("unsupported provider type %s for creating volume attributes class", sharedPool.GetProvider().GetType().String())
	}

	for key, storage := range storages {
		if sharedPool.GetProvider().GetType() == csngapiv1beta.ProviderType_ALICLOUD ||
			sharedPool.GetProvider().GetType() == csngapiv1beta.ProviderType_AZURE {
			vac := buildVolumeAttributesClassV1(key, driverName, storage)
			if err := k8s.CreateIfNotExist(ctx, remoteCli, vac); err != nil {
				tilog.Error(ctx, "failed to create volume attributes class", zap.String("vac", vac.String()), zap.Error(err))
				return fmt.Errorf("failed to create volume attributes class: %w", err)
			}
		} else {
			vac := buildVolumeAttributesClassV1Beta1(key, driverName, storage)
			if err := k8s.CreateIfNotExist(ctx, remoteCli, vac); err != nil {
				tilog.Error(ctx, "failed to create volume attributes class", zap.String("vac", vac.String()), zap.Error(err))
				return fmt.Errorf("failed to create volume attributes class: %w", err)
			}
		}
	}
	return nil
}

func buildVolumeAttributesClassV1Beta1(name, driverName string, storage *entity.Storage) *storagev1beta1.VolumeAttributesClass {
	vac := new(storagev1beta1.VolumeAttributesClass)
	vac.Name = name
	vac.DriverName = driverName

	switch storage.GetType() {
	case entity.GP3:
		vac.Parameters = map[string]string{
			"type":       storage.GetType(),
			"iops":       fmt.Sprintf("%d", storage.GetIops()),
			"throughput": fmt.Sprintf("%d", storage.GetThroughputMbs()),
		}
	case entity.IO2:
		// io2 only config iops
		vac.Parameters = map[string]string{
			"type": storage.GetType(),
			"iops": fmt.Sprintf("%d", storage.GetIops()),
		}
	case entity.CloudAuto:
		vac.Parameters = map[string]string{
			"provisionedIops": fmt.Sprintf("%d", storage.GetIops()),
		}
	case entity.PremiumSSDv2:
		vac.Parameters = map[string]string{
			"skuName":           "PremiumV2_LRS",
			"DiskIOPSReadWrite": fmt.Sprintf("%d", storage.GetIops()),
			"DiskMBpsReadWrite": fmt.Sprintf("%d", storage.GetThroughputMbs()),
		}
	}
	return vac
}

func buildVolumeAttributesClassV1(name, driverName string, storage *entity.Storage) *storagev1.VolumeAttributesClass {
	vac := new(storagev1.VolumeAttributesClass)
	vac.Name = name
	vac.DriverName = driverName

	switch storage.GetType() {
	case entity.GP3:
		vac.Parameters = map[string]string{
			"type":       storage.GetType(),
			"iops":       fmt.Sprintf("%d", storage.GetIops()),
			"throughput": fmt.Sprintf("%d", storage.GetThroughputMbs()),
		}
	case entity.IO2:
		// io2 only config iops
		vac.Parameters = map[string]string{
			"type": storage.GetType(),
			"iops": fmt.Sprintf("%d", storage.GetIops()),
		}
	case entity.CloudAuto:
		vac.Parameters = map[string]string{
			"provisionedIops": fmt.Sprintf("%d", storage.GetIops()),
		}
	case entity.PremiumSSDv2:
		vac.Parameters = map[string]string{
			"skuName":           "PremiumV2_LRS",
			"DiskIOPSReadWrite": fmt.Sprintf("%d", storage.GetIops()),
			"DiskMBpsReadWrite": fmt.Sprintf("%d", storage.GetThroughputMbs()),
		}
	}
	return vac
}
