package action

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/gkeheterogeneous"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	DeleteGKEHeterogeneousDeployBeforeScaleActionName = "delete_gke_heterogeneous_deploy_before_scale"
)

type GKEHeterogeneousScaleActionParameter struct {
	clusterRepo   clusterrepo.Repo
	componentRepo clusterrepo.ComponentRepo
	regionRepo    region.Repo
	regionalCli   regionserver.Client
}

type DeleteGKEHeterogeneousDeployBeforeScaleAction struct {
	parameter *GKEHeterogeneousScaleActionParameter
}

func NewDeleteGKEHeterogeneousDeployBeforeScaleAction(
	clusterRepo clusterrepo.Repo,
	componentRepo clusterrepo.ComponentRepo,
	regionRepo region.Repo,
	regionalCli regionserver.Client,
) *DeleteGKEHeterogeneousDeployBeforeScaleAction {
	return &DeleteGKEHeterogeneousDeployBeforeScaleAction{
		parameter: &GKEHeterogeneousScaleActionParameter{
			clusterRepo:   clusterRepo,
			componentRepo: componentRepo,
			regionRepo:    regionRepo,
			regionalCli:   regionalCli,
		},
	}
}

func (a *DeleteGKEHeterogeneousDeployBeforeScaleAction) Name() string {
	return DeleteGKEHeterogeneousDeployBeforeScaleActionName
}

func (a *DeleteGKEHeterogeneousDeployBeforeScaleAction) ParameterNew() any {
	return &ScaleActionParameter{}
}

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

	target, ok, err := a.parameter.gkeHeterogeneousScaleTarget(cctx, ctx, params, true)
	if err != nil {
		tilog.Error(cctx, "prepare gke heterogeneous delete-before-scale target failed", zap.Error(err))
		return err
	}
	if !ok {
		return nil
	}

	if berr := gkeheterogeneous.CleanupGatekeeperAssign(
		cctx, a.parameter.regionalCli, target.region,
		target.clusterID, target.componentType, target.tidbGroupID,
	); berr != nil {
		tilog.Error(cctx, "cleanup gke heterogeneous gatekeeper failed", zap.Error(berr))
		return berr
	}

	return nil
}

type gkeHeterogeneousScaleTarget struct {
	clusterID     string
	tidbGroupID   string
	componentType regionaldedicatedv1.ComponentType
	region        *entity.Region
	currentMu     string
}

func (p *GKEHeterogeneousScaleActionParameter) gkeHeterogeneousScaleTarget(
	ctx context.Context,
	runCtx run.ExecuteContext,
	params any,
	requireDeploy bool,
) (*gkeHeterogeneousScaleTarget, bool, error) {
	req, err := getRequestBody(ctx, params)
	if err != nil {
		return nil, false, err
	}
	if req.NeedToScaleInstanceMu == nil ||
		!req.NeedToScaleInstanceMu.InstanceMuChanged {
		return nil, false, nil
	}
	componentType := common.ScaleComponentTypeFromString(req.ComponentType)
	if componentType == globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		return nil, false, nil
	}

	componentEn, err := p.clusterComponent(ctx, req)
	if err != nil {
		return nil, false, err
	}
	clusterIDFromCtx, _ := runCtx.GetVar(common.KeyClusterId)
	if clusterIDFromCtx != "" && fmt.Sprintf("%d", componentEn.GetClusterId()) != clusterIDFromCtx {
		return nil, false, fmt.Errorf("component %d does not match cluster %s", componentEn.Id, clusterIDFromCtx)
	}
	clusterEn, err := p.clusterRepo.Get(ctx, componentEn.GetClusterId(), false)
	if err != nil {
		return nil, false, err
	}
	if clusterEn.GetCloudProviderId() != globalcommonv1.CloudProvider_GCP {
		return nil, false, nil
	}
	regionEn, err := p.regionRepo.SelectRegionById(ctx, clusterEn.RegionId, clusterEn.GetOrganizationId())
	if err != nil {
		return nil, false, err
	}
	target := &gkeHeterogeneousScaleTarget{
		clusterID:     strconv.FormatUint(clusterEn.ClusterId, 10),
		componentType: toRegionalGKEHeterogeneousScaleComponentType(componentType),
		region:        regionEn,
		currentMu:     req.NeedToScaleInstanceMu.CurrentInstanceMu,
	}
	if componentType == globaldedicatedv1.ComponentType_TIDB {
		target.tidbGroupID = strconv.FormatUint(componentEn.GetTidbGroupId(), 10)
	}

	if requireDeploy {
		return target, true, nil
	}

	_, deployExists, berr := gkeheterogeneous.GetDeploy(ctx, p.regionalCli, regionEn, target.clusterID, target.componentType, target.tidbGroupID)
	if berr != nil {
		return nil, false, berr
	}
	if !deployExists {
		return target, true, nil
	}
	// Cleanup runs after delete-before-scale. If the deploy still exists, keep
	// resources untouched because the mixed-deploy path is still active.
	tilog.Warn(
		ctx,
		"skip gke heterogeneous cleanup because deploy still exists",
		zap.String("cluster_id", target.clusterID),
		zap.String("component_type", target.componentType.String()),
		zap.String("tidb_group_id", target.tidbGroupID),
	)
	return nil, false, nil
}

func (p *GKEHeterogeneousScaleActionParameter) clusterComponent(
	ctx context.Context,
	req *ScaleComponentParameter,
) (*entity.ClusterComponent, error) {
	return p.componentRepo.Load(ctx, uint(req.ID))
}

func toRegionalGKEHeterogeneousScaleComponentType(componentType globaldedicatedv1.ComponentType) regionaldedicatedv1.ComponentType {
	regionalComponent, ok := gkeheterogeneous.ToRegionalComponentType(componentType)
	if !ok {
		return regionaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED
	}
	return regionalComponent
}
