package action

import (
	"context"
	"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"
	regionrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/tidbgroup"
	"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/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

type DeleteGKEHeterogeneousGatekeeperAssignAction struct {
	clusterRepo clusterrepo.Repo
	regionRepo  regionrepo.Repo
	regionalCli regionserver.Client
}

type DeleteGKEHeterogeneousDeployAction struct {
	clusterRepo clusterrepo.Repo
	regionRepo  regionrepo.Repo
	regionalCli regionserver.Client
}

func NewDeleteGKEHeterogeneousGatekeeperAssignAction(
	clusterRepo clusterrepo.Repo,
	regionRepo regionrepo.Repo,
	regionalCli regionserver.Client,
) *DeleteGKEHeterogeneousGatekeeperAssignAction {
	return &DeleteGKEHeterogeneousGatekeeperAssignAction{
		clusterRepo: clusterRepo,
		regionRepo:  regionRepo,
		regionalCli: regionalCli,
	}
}

func NewDeleteGKEHeterogeneousDeployAction(
	clusterRepo clusterrepo.Repo,
	regionRepo regionrepo.Repo,
	regionalCli regionserver.Client,
) *DeleteGKEHeterogeneousDeployAction {
	return &DeleteGKEHeterogeneousDeployAction{
		clusterRepo: clusterRepo,
		regionRepo:  regionRepo,
		regionalCli: regionalCli,
	}
}

func (a *DeleteGKEHeterogeneousGatekeeperAssignAction) Name() string {
	return DeleteGKEHeterogeneousGatekeeperAssignActionName
}

func (a *DeleteGKEHeterogeneousDeployAction) Name() string {
	return DeleteGKEHeterogeneousDeployActionName
}

func (a *DeleteGKEHeterogeneousGatekeeperAssignAction) ParameterNew() any {
	return &gkeHeterogeneousDeployActionParam{}
}

func (a *DeleteGKEHeterogeneousDeployAction) ParameterNew() any {
	return &gkeHeterogeneousDeployActionParam{}
}

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

	tilog.Info(cctx, "start to delete gke heterogeneous gatekeeper assign")
	defer func() {
		tilog.Info(cctx, "end to delete gke heterogeneous gatekeeper assign")
	}()

	req, err := parseDeleteGKEHeterogeneousDeployRequest(cctx, params)
	if err != nil {
		tilog.Error(cctx, "parse delete gke heterogeneous deploy request failed", zap.Error(err))
		return err
	}

	skip, err := skipIfClusterDeleted(cctx, a.clusterRepo, req.GetClusterId())
	if err != nil {
		tilog.Error(cctx, "check cluster deleted failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	if skip {
		return nil
	}

	target, err := prepareGKEHeterogeneousDeleteTargetOnly(cctx, a.clusterRepo, a.regionRepo, req)
	if err != nil {
		tilog.Error(
			cctx,
			"prepare gke heterogeneous gatekeeper assign delete target failed",
			zap.Uint64("cluster_id", req.GetClusterId()),
			zap.Error(err),
		)
		return err
	}
	assign, berr := a.regionalCli.GetGKEHeterogeneousGatekeeperAssign(
		cctx,
		target.region,
		&regionaldedicatedv1.GetGKEHeterogeneousGatekeeperAssignRequest{
			ClusterId:     target.clusterID,
			ComponentType: target.componentType,
			TidbGroupId:   target.tidbGroupID,
		},
	)
	if berr != nil && berr.ErrCode() != bizerr.ErrCodeRecordNotFound {
		tilog.Error(cctx, "get gke heterogeneous gatekeeper assign failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(berr))
		return berr
	}
	if assign == nil || assign.GetName() == "" {
		return nil
	}
	if err := a.regionalCli.DeleteGKEHeterogeneousGatekeeperAssign(
		cctx,
		target.region,
		&regionaldedicatedv1.DeleteGKEHeterogeneousGatekeeperAssignRequest{
			ClusterId: target.clusterID,
			Name:      assign.GetName(),
		},
	); err != nil && err.ErrCode() != bizerr.ErrCodeRecordNotFound {
		tilog.Error(cctx, "delete gke heterogeneous gatekeeper assign failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	return nil
}

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

	tilog.Info(cctx, "start to delete gke heterogeneous deploy")
	defer func() {
		tilog.Info(cctx, "end to delete gke heterogeneous deploy")
	}()

	req, err := parseDeleteGKEHeterogeneousDeployRequest(cctx, params)
	if err != nil {
		tilog.Error(cctx, "parse delete gke heterogeneous deploy request failed", zap.Error(err))
		return err
	}

	skip, err := skipIfClusterDeleted(cctx, a.clusterRepo, req.GetClusterId())
	if err != nil {
		tilog.Error(cctx, "check cluster deleted failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	if skip {
		return nil
	}

	target, err := prepareGKEHeterogeneousDeleteTargetOnly(cctx, a.clusterRepo, a.regionRepo, req)
	if err != nil {
		tilog.Error(cctx, "prepare gke heterogeneous deploy delete target failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	if err := a.regionalCli.DeleteGKEHeterogeneousDeploy(
		cctx,
		target.region,
		&regionaldedicatedv1.DeleteGKEHeterogeneousDeployRequest{
			ClusterId:     target.clusterID,
			ComponentType: target.componentType,
			TidbGroupId:   target.tidbGroupID,
		},
	); err != nil && err.ErrCode() != bizerr.ErrCodeRecordNotFound {
		tilog.Error(cctx, "delete gke heterogeneous deploy failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	return nil
}

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

func prepareGKEHeterogeneousDeleteTargetOnly(
	ctx context.Context,
	clusterRepo clusterrepo.Repo,
	regionRepo regionrepo.Repo,
	req *globaldedicatedv1.DeleteGKEHeterogeneousDeployRequest,
) (*gkeHeterogeneousDeleteTarget, bizerr.BizError) {
	_, regionEn, err := gkeheterogeneous.Prepare(
		ctx,
		clusterRepo,
		regionRepo,
		nil,
		req.GetTenantId(),
		req.GetProjectId(),
		req.GetClusterId(),
		req.GetComponentType(),
		true,
		false,
	)
	if err != nil {
		return nil, err
	}
	componentType, err := gkeheterogeneous.ToRegionalComponentTypeOrError(req.GetComponentType())
	if err != nil {
		return nil, err
	}
	return &gkeHeterogeneousDeleteTarget{
		region:        regionEn,
		clusterID:     strconv.FormatUint(req.GetClusterId(), 10),
		componentType: componentType,
		tidbGroupID:   formatOptionalUint64(req.GetTidbGroupId()),
	}, nil
}

const DeleteGKEHeterogeneousBeforeDeleteClusterActionName = "delete_gke_heterogeneous_before_delete_cluster"

type DeleteGKEHeterogeneousBeforeDeleteClusterAction struct {
	regionalCli   regionserver.Client
	clusterRepo   clusterrepo.Repo
	regionRepo    regionrepo.Repo
	tidbGroupRepo tidbgroup.Repo
}

func NewDeleteGKEHeterogeneousBeforeDeleteClusterAction(
	regionalCli regionserver.Client,
	clusterRepo clusterrepo.Repo,
	regionRepo regionrepo.Repo,
	tidbGroupRepo tidbgroup.Repo,
) *DeleteGKEHeterogeneousBeforeDeleteClusterAction {
	return &DeleteGKEHeterogeneousBeforeDeleteClusterAction{
		regionalCli:   regionalCli,
		clusterRepo:   clusterRepo,
		regionRepo:    regionRepo,
		tidbGroupRepo: tidbGroupRepo,
	}
}

func (a *DeleteGKEHeterogeneousBeforeDeleteClusterAction) Name() string {
	return DeleteGKEHeterogeneousBeforeDeleteClusterActionName
}

func (a *DeleteGKEHeterogeneousBeforeDeleteClusterAction) ParameterNew() any {
	return &common.ClusterIdParameter{}
}

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

	tilog.Info(cctx, "start to delete gke heterogeneous before delete cluster")
	defer func() {
		tilog.Info(cctx, "end to delete gke heterogeneous before delete cluster")
	}()

	clusterID, err := common.GetClusterId(params)
	if err != nil {
		tilog.Error(cctx, "get cluster id from params failed", zap.Error(err))
		return err
	}

	cluster, err := a.clusterRepo.Get(cctx, clusterID, true)
	if err != nil {
		tilog.Error(cctx, "get cluster for gke heterogeneous cleanup failed", zap.Uint64("cluster_id", clusterID), zap.Error(err))
		return err
	}

	return a.cleanupGKEHeterogeneous(cctx, cluster)
}

func (a *DeleteGKEHeterogeneousBeforeDeleteClusterAction) cleanupGKEHeterogeneous(
	ctx context.Context,
	cluster *entity.Cluster,
) error {
	if cluster.CloudProviderId != globalcommonv1.CloudProvider_GCP {
		return nil
	}

	region, err := a.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.GetOrganizationId())
	if err != nil {
		tilog.Error(ctx, "select region for gke heterogeneous cleanup failed",
			zap.Uint64("cluster_id", cluster.ClusterId), zap.Error(err))
		return err
	}

	clusterIDStr := strconv.FormatUint(cluster.ClusterId, 10)

	for _, compType := range gkeheterogeneous.SupportedRegionalHeterogeneousClusterLevelComponentTypes() {
		if err := a.cleanupGKEHeterogeneousForComponent(ctx, a.regionalCli, region, clusterIDStr, compType, ""); err != nil {
			return err
		}
	}

	groups, qErr := a.tidbGroupRepo.Query(ctx, &tidbgroup.Filter{ClusterIds: []uint64{cluster.ClusterId}, ShowDeleted: true})
	if qErr != nil {
		tilog.Error(ctx, "query tidb groups for gke heterogeneous cleanup failed",
			zap.Uint64("cluster_id", cluster.ClusterId), zap.Error(qErr))
		return qErr
	}
	for _, group := range groups {
		gid := strconv.FormatUint(group.Id, 10)
		for _, compType := range gkeheterogeneous.SupportedRegionalHeterogeneousGroupLevelComponentTypes() {
			if err := a.cleanupGKEHeterogeneousForComponent(ctx, a.regionalCli, region, clusterIDStr, compType, gid); err != nil {
				return err
			}
		}
	}

	return nil
}

func (a *DeleteGKEHeterogeneousBeforeDeleteClusterAction) cleanupGKEHeterogeneousForComponent(
	ctx context.Context,
	cli regionserver.Client,
	regionEn *entity.Region,
	clusterIDStr string,
	compType regionaldedicatedv1.ComponentType,
	tidbGroupID string,
) error {
	_, active, derr := gkeheterogeneous.GetDeploy(ctx, cli, regionEn, clusterIDStr, compType, tidbGroupID)
	if derr != nil {
		if derr.ErrCode() == bizerr.ErrCodeRecordNotFound {
			return nil
		}
		tilog.Error(ctx, "failed to check gke heterogeneous deploy",
			zap.String("cluster_id", clusterIDStr),
			zap.String("component_type", compType.String()),
			zap.String("tidb_group_id", tidbGroupID),
			zap.Error(derr))
		return derr
	}
	if !active {
		return nil
	}
	if berr := gkeheterogeneous.CleanupGatekeeperAssign(
		ctx, cli, regionEn, clusterIDStr, compType, tidbGroupID,
	); berr != nil {
		tilog.Error(ctx, "cleanup gke heterogeneous gatekeeper failed",
			zap.String("cluster_id", clusterIDStr),
			zap.String("component_type", compType.String()),
			zap.String("tidb_group_id", tidbGroupID),
			zap.Error(berr))
		return berr
	}
	if berr := gkeheterogeneous.CleanupDeployIntent(
		ctx, cli, regionEn, clusterIDStr, compType, tidbGroupID,
	); berr != nil {
		tilog.Error(ctx, "cleanup gke heterogeneous deploy intent failed",
			zap.String("cluster_id", clusterIDStr),
			zap.String("component_type", compType.String()),
			zap.String("tidb_group_id", tidbGroupID),
			zap.Error(berr))
		return berr
	}
	return nil
}
