package action

import (
	"errors"
	"strings"

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

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository"
	workflowcommon "github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	CreateCluster = "create_cluster"
)

type CreateClusterParameter struct {
	ClusterId   string `json:"cluster_id"`
	RequestBody string `json:"request_body"`
}

type CreateClusterAction struct {
	clusterRepo     repository.ClusterRepo
	regionalService adapter.RegionalService
	sys9Lifecycle   adapter.Sys9Lifecycle
}

func NewCreateClusterAction(
	clusterRepo repository.ClusterRepo,
	regionalService adapter.RegionalService,
	sys9Lifecycle adapter.Sys9Lifecycle,
) *CreateClusterAction {
	return &CreateClusterAction{
		clusterRepo:     clusterRepo,
		regionalService: regionalService,
		sys9Lifecycle:   sys9Lifecycle,
	}
}

func (a *CreateClusterAction) Name() string {
	return CreateCluster
}

func (a *CreateClusterAction) ParameterNew() any {
	return &CreateClusterParameter{}
}

func (a *CreateClusterAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)

	createClusterParam, ok := params.(*CreateClusterParameter)
	if !ok {
		tilog.Error(cctx, "expect CreateClusterParameter parameter")
		return errors.New("expect CreateClusterParameter parameter")
	}
	var err error
	tilog.Info(cctx, "start to create cluster", zap.String("cluster_id", createClusterParam.ClusterId))
	defer func() {
		if err != nil {
			tilog.Error(
				cctx,
				"failed to create cluster",
				zap.String("cluster_id", createClusterParam.ClusterId),
				zap.Error(err),
			)
		} else {
			tilog.Info(cctx, "end to create cluster", zap.String("cluster_id", createClusterParam.ClusterId))
		}

		span.End()
	}()

	var req globalserverlessv1.ServerlessServiceCreateRequest
	if err := protojson.Unmarshal([]byte(createClusterParam.RequestBody), &req); err != nil {
		return err
	}

	var clusterEn *entity.Cluster
	clusterEn, err = a.clusterRepo.Get(cctx, createClusterParam.ClusterId, true)
	if err != nil {
		return err
	}

	if !clusterEn.DeletedAt.IsZero() {
		tilog.Warn(
			cctx,
			"the cluster is deleted, interrupt initialize task",
			zap.String("cluster", createClusterParam.ClusterId),
		)
		return nil
	}

	if isPGStarterCluster(clusterEn) {
		var created *adapter.Sys9CreateClusterResult
		created, err = a.sys9Lifecycle.CreateCluster(cctx, clusterEn)
		if err != nil {
			return err
		}
		if created.OperationID == "" {
			return errors.New("empty operation id from create pg cluster")
		}
		clusterEn.ExternalInstanceID = created.ServiceInstanceID
		if saveErr := a.clusterRepo.Save(cctx, clusterEn); saveErr != nil {
			return saveErr
		}
		ctx.ShareData().Set(workflowcommon.KeyOperationId, created.OperationID)
		return nil
	}

	_, err = a.regionalService.CreateCluster(cctx, clusterEn.Region, &req)
	return err
}

func isPGStarterCluster(cluster *entity.Cluster) bool {
	if cluster == nil || cluster.Region == nil {
		return false
	}
	if cluster.ServicePlan != globalcommonv1.ServicePlan_Starter {
		return false
	}
	return strings.EqualFold(cluster.EngineType, "POSTGRESQL_COMPATIBLE")
}
