package action

import (
	"errors"
	"fmt"
	"strconv"

	"braces.dev/errtrace"
	"github.com/samber/lo"
	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dedicatedglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

type CreateMigrationClusterAction struct {
	migrationClusterRepo repository.MigrationClusterRepo
	dedicatedCli         dedicatedglobal.DedicatedGlobal
}

func NewCreateMigrationClusterAction(
	migrationClusterRepo repository.MigrationClusterRepo,
	dedicatedCli dedicatedglobal.DedicatedGlobal,
) *CreateMigrationClusterAction {
	return &CreateMigrationClusterAction{
		migrationClusterRepo: migrationClusterRepo,
		dedicatedCli:         dedicatedCli,
	}
}

func (c *CreateMigrationClusterAction) Name() string {
	return constant.TaskDedicatedCreateMigrationCluster
}

func (c *CreateMigrationClusterAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)
	defer span.End()

	tilog.Info(cctx, "create migration cluster action start")

	migrationClusterIdStr, ok := ctx.GetVar(constant.KeyMigrationClusterID)
	if !ok {
		return fmt.Errorf("migration cluster id not found")
	}
	migrationClusterID, err := strconv.ParseUint(migrationClusterIdStr, 10, 64)
	if err != nil {
		return fmt.Errorf("parse migration cluster id %w", err)
	}

	requestJson, ok := ctx.GetVar(constant.KeyRequest)
	if !ok {
		return errtrace.Wrap(errors.New("create dedicated request is not found"))
	}

	var request globaldedicatedv1.CreateClusterRequest
	if err := protojson.Unmarshal([]byte(requestJson), &request); err != nil {
		return fmt.Errorf("unmarshal create dedicated request failed: %w", err)
	}

	migrationCluster, err := c.migrationClusterRepo.Load(cctx, migrationClusterID)
	if err != nil {
		return errtrace.Wrap(err)
	}
	migrationCluster.State = tidbmgmtservicev1.MigrationCluster_RUNNING
	if err := c.migrationClusterRepo.Save(cctx, migrationCluster); err != nil {
		return errtrace.Wrap(err)
	}

	sourceClusterId := migrationCluster.SourceClusterId

	// set cloud admin password
	getClusterConfidentialInfoResponse, err := c.dedicatedCli.GetClusterConfidentialInfo(
		cctx,
		&globaldedicatedv1.GetClusterConfidentialInfoRequest{
			ClusterId: sourceClusterId,
		},
	)
	if err != nil {
		tilog.Error(
			cctx,
			"failed to get cloud admin password of source cluster",
			zap.Error(err),
			zap.Uint64("migration_cluster_id", migrationClusterID),
			zap.Uint64("source_cluster_id", sourceClusterId),
		)
		return fmt.Errorf("failed to get cloud admin password of source cluster: %w", err)
	}
	if getClusterConfidentialInfoResponse.EncryptedCloudAdminPassword == "" {
		tilog.Error(
			cctx,
			"cloud admin password of source cluster is empty",
			zap.Uint64("migration_cluster_id", migrationClusterID),
			zap.Uint64("source_cluster_id", sourceClusterId),
		)
		return fmt.Errorf("cloud admin password of source cluster is empty")
	}
	request.Cluster.EncryptedCloudAdminPassword = getClusterConfidentialInfoResponse.EncryptedCloudAdminPassword

	// set cluster custom parameters configuration
	getClusterCustomParamsResponse, err := c.dedicatedCli.GetClusterCustomParams(
		cctx,
		&globaldedicatedv1.GetClusterCustomParamsRequest{
			ClusterId: sourceClusterId,
		},
	)
	if err != nil {
		tilog.Error(
			cctx,
			"failed to get custom params of source cluster",
			zap.Error(err),
			zap.Uint64("migration_cluster_id", migrationClusterID),
			zap.Uint64("source_cluster_id", sourceClusterId),
		)
		return fmt.Errorf("failed to get custom params of source cluster: %w", err)
	}
	request.ClusterConfiguration = getClusterCustomParamsResponse.ClusterConfiguration
	request.OverwriteTenantInitConfig = true

	// set cluster custom image tag
	if request.ClusterConfiguration == nil {
		request.ClusterConfiguration = &globaldedicatedv1.ClusterConfiguration{}
	}
	componentConfigurations := lo.SliceToMap(
		request.ClusterConfiguration.ComponentConfigurations,
		func(c *globaldedicatedv1.ClusterConfiguration_ComponentConfiguration) (string, *globaldedicatedv1.ClusterConfiguration_ComponentConfiguration) {
			return c.GetComponentType().String(), c
		},
	)

	setOverwriteImageTag := func(
		componentType globaldedicatedv1.ComponentType,
		imageTag string,
	) {
		if imageTag == "" {
			return
		}

		key := componentType.String()
		cfg, ok := componentConfigurations[key]
		if !ok {
			cfg = &globaldedicatedv1.ClusterConfiguration_ComponentConfiguration{
				ComponentType: componentType,
			}
		}
		cfg.OverwriteImageTag = imageTag
		componentConfigurations[key] = cfg
	}

	setOverwriteImageTag(globaldedicatedv1.ComponentType_TIDB, migrationCluster.GetCreateClusterTidbImageTag())
	setOverwriteImageTag(globaldedicatedv1.ComponentType_TIKV, migrationCluster.GetCreateClusterTikvImageTag())
	setOverwriteImageTag(globaldedicatedv1.ComponentType_PD, migrationCluster.GetCreateClusterPdImageTag())
	setOverwriteImageTag(globaldedicatedv1.ComponentType_TIFLASH, migrationCluster.GetCreateClusterTiflashImageTag())

	request.ClusterConfiguration.ComponentConfigurations = lo.MapToSlice(
		componentConfigurations,
		func(_ string, c *globaldedicatedv1.ClusterConfiguration_ComponentConfiguration) *globaldedicatedv1.ClusterConfiguration_ComponentConfiguration {
			return c
		},
	)

	// create migration cluster
	_, err = c.dedicatedCli.CreateCluster(cctx, &request)
	if err != nil {
		tilog.Error(
			cctx,
			"create migration cluster failed",
			zap.Error(err),
			zap.Uint64("migration_cluster_id", migrationClusterID),
			zap.Uint64("source_cluster_id", sourceClusterId),
			zap.Uint64("target_cluster_id", migrationCluster.GetTargetClusterId()),
		)
		return errtrace.Wrap(err)
	}

	tilog.Info(
		cctx,
		"create migration cluster action end",
		zap.Uint64("migration_cluster_id", migrationClusterID),
		zap.Uint64("source_cluster_id", sourceClusterId),
		zap.Uint64("target_cluster_id", migrationCluster.GetTargetClusterId()),
	)
	return nil
}
