package action

import (
	"context"
	"strings"
	"time"

	"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"

	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/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

type PollUntilGKEHeterogeneousDeployReadyForGatekeeperAction struct {
	clusterRepo         clusterrepo.Repo
	regionRepo          region.Repo
	tidbGroupRepo       tidbgroup.Repo
	regionalCli         regionserver.Client
	readyStabilityDelay time.Duration
}

type CreateGKEHeterogeneousGatekeeperAssignAction struct {
	clusterRepo   clusterrepo.Repo
	regionRepo    region.Repo
	tidbGroupRepo tidbgroup.Repo
	regionalCli   regionserver.Client
}

type gkeGatekeeperTarget struct {
	region        *entity.Region
	clusterID     string
	componentType regionaldedicatedv1.ComponentType
	tidbGroupID   string
	tidbGroupKind regionaldedicatedv1.TiDBGroupKind
}

func NewPollUntilGKEHeterogeneousDeployReadyForGatekeeperAction(
	clusterRepo clusterrepo.Repo,
	regionRepo region.Repo,
	tidbGroupRepo tidbgroup.Repo,
	regionalCli regionserver.Client,
) *PollUntilGKEHeterogeneousDeployReadyForGatekeeperAction {
	return &PollUntilGKEHeterogeneousDeployReadyForGatekeeperAction{
		clusterRepo:         clusterRepo,
		regionRepo:          regionRepo,
		tidbGroupRepo:       tidbGroupRepo,
		regionalCli:         regionalCli,
		readyStabilityDelay: 3 * time.Second,
	}
}

func NewCreateGKEHeterogeneousGatekeeperAssignAction(
	clusterRepo clusterrepo.Repo,
	regionRepo region.Repo,
	tidbGroupRepo tidbgroup.Repo,
	regionalCli regionserver.Client,
) *CreateGKEHeterogeneousGatekeeperAssignAction {
	return &CreateGKEHeterogeneousGatekeeperAssignAction{
		clusterRepo:   clusterRepo,
		regionRepo:    regionRepo,
		tidbGroupRepo: tidbGroupRepo,
		regionalCli:   regionalCli,
	}
}

func (a *PollUntilGKEHeterogeneousDeployReadyForGatekeeperAction) Name() string {
	return PollUntilGKEHeterogeneousDeployReadyForGatekeeperActionName
}

func (a *CreateGKEHeterogeneousGatekeeperAssignAction) Name() string {
	return CreateGKEHeterogeneousGatekeeperAssignActionName
}

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

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

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

	tilog.Info(cctx, "start to poll until gke heterogeneous deploy ready for gatekeeper")
	defer func() {
		tilog.Info(cctx, "end to poll until gke heterogeneous deploy ready for gatekeeper")
	}()

	req, err := parseApplyGKEHeterogeneousDeployRequest(cctx, params)
	if err != nil {
		tilog.Error(cctx, "parse apply 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 := a.prepare(cctx, req)
	if err != nil {
		tilog.Error(cctx, "prepare gke heterogeneous gatekeeper poll failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}

	deploy, err := a.regionalCli.GetGKEHeterogeneousDeploy(
		cctx,
		target.region,
		&regionaldedicatedv1.GetGKEHeterogeneousDeployRequest{
			ClusterId:     target.clusterID,
			ComponentType: target.componentType,
			TidbGroupId:   target.tidbGroupID,
		},
	)
	if err != nil {
		tilog.Error(cctx, "get gke heterogeneous deploy failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	if err := checkGKEHeterogeneousDeployReadyForGatekeeper(deploy); err != nil {
		tilog.Error(cctx, "gke heterogeneous deploy is not ready for gatekeeper", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}

	if err := a.checkReadyStability(
		cctx,
		target.region,
		target.clusterID,
		target.componentType,
		target.tidbGroupID,
	); err != nil {
		tilog.Error(cctx, "gke heterogeneous deploy readiness not stable", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	return nil
}

func (a *PollUntilGKEHeterogeneousDeployReadyForGatekeeperAction) checkReadyStability(
	ctx context.Context,
	regionEn *entity.Region,
	clusterID string,
	componentType regionaldedicatedv1.ComponentType,
	tidbGroupID string,
) error {
	delay := a.readyStabilityDelay
	if delay <= 0 {
		delay = 3 * time.Second
	}
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-time.After(delay):
	}
	deploy, err := a.regionalCli.GetGKEHeterogeneousDeploy(
		ctx,
		regionEn,
		&regionaldedicatedv1.GetGKEHeterogeneousDeployRequest{
			ClusterId:     clusterID,
			ComponentType: componentType,
			TidbGroupId:   tidbGroupID,
		},
	)
	if err != nil {
		return err
	}
	return checkGKEHeterogeneousDeployReadyForGatekeeper(deploy)
}

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

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

	req, err := parseApplyGKEHeterogeneousDeployRequest(cctx, params)
	if err != nil {
		tilog.Error(cctx, "parse apply 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 := a.prepare(cctx, req)
	if err != nil {
		tilog.Error(cctx, "prepare gke heterogeneous gatekeeper assign failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}

	deploy, err := a.regionalCli.GetGKEHeterogeneousDeploy(
		cctx,
		target.region,
		&regionaldedicatedv1.GetGKEHeterogeneousDeployRequest{
			ClusterId:     target.clusterID,
			ComponentType: target.componentType,
			TidbGroupId:   target.tidbGroupID,
		},
	)
	if err != nil {
		tilog.Error(cctx, "get gke heterogeneous deploy failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	if deploy == nil || !deploy.GetEnabled() {
		err := bizerr.New(bizerr.ErrCodeRecordNotFound, "gke heterogeneous deploy does not exist")
		tilog.Error(cctx, "gke heterogeneous deploy does not exist", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	if err := checkGKEHeterogeneousDeployReadyForGatekeeper(deploy); err != nil {
		tilog.Error(cctx, "gke heterogeneous deploy is not ready for gatekeeper", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}

	if err := a.ensureGatekeeperAssign(
		cctx,
		target,
		deploy,
	); err != nil {
		tilog.Error(cctx, "create gke heterogeneous gatekeeper assign failed", zap.Uint64("cluster_id", req.GetClusterId()), zap.Error(err))
		return err
	}
	return nil
}

func (a *PollUntilGKEHeterogeneousDeployReadyForGatekeeperAction) prepare(
	ctx context.Context,
	req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
) (*gkeGatekeeperTarget, bizerr.BizError) {
	return newGKEGatekeeperTarget(ctx, a.clusterRepo, a.regionRepo, a.tidbGroupRepo, req)
}

func (a *CreateGKEHeterogeneousGatekeeperAssignAction) prepare(
	ctx context.Context,
	req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
) (*gkeGatekeeperTarget, bizerr.BizError) {
	return newGKEGatekeeperTarget(ctx, a.clusterRepo, a.regionRepo, a.tidbGroupRepo, req)
}

func (a *CreateGKEHeterogeneousGatekeeperAssignAction) ensureGatekeeperAssign(
	ctx context.Context,
	target *gkeGatekeeperTarget,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
) bizerr.BizError {
	assign, err := a.regionalCli.GetGKEHeterogeneousGatekeeperAssign(
		ctx,
		target.region,
		&regionaldedicatedv1.GetGKEHeterogeneousGatekeeperAssignRequest{
			ClusterId:     target.clusterID,
			ComponentType: target.componentType,
			TidbGroupId:   target.tidbGroupID,
		},
	)
	if err != nil && err.ErrCode() != bizerr.ErrCodeRecordNotFound {
		return err
	}
	if assign != nil && assign.GetName() != "" {
		return validateExistingGatekeeperAssign(assign, target, deploy)
	}
	if err := checkGKEHeterogeneousDeployReadyForGatekeeper(deploy); err != nil {
		return err
	}
	computeClassName := deploy.GetComputeClassName()
	if computeClassName == "" {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "empty compute class name in gke heterogeneous deploy")
	}
	_, err = a.regionalCli.CreateGKEHeterogeneousGatekeeperAssign(
		ctx,
		target.region,
		&regionaldedicatedv1.CreateGKEHeterogeneousGatekeeperAssignRequest{
			ClusterId:        target.clusterID,
			ComponentType:    target.componentType,
			ComputeClassName: computeClassName,
			TidbGroupId:      target.tidbGroupID,
			TidbGroupKind:    target.tidbGroupKind,
		},
	)
	if err != nil && err.ErrCode() != bizerr.ErrCodeRecordExist {
		return err
	}
	return nil
}

func validateExistingGatekeeperAssign(
	assign *regionaldedicatedv1.GKEHeterogeneousGatekeeperAssign,
	target *gkeGatekeeperTarget,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
) bizerr.BizError {
	if assign == nil || strings.TrimSpace(assign.GetName()) == "" {
		return nil
	}
	if target == nil {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "empty gke heterogeneous gatekeeper target")
	}
	if assign.GetComponentType() != target.componentType {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous gatekeeper assign component mismatch: got %s, want %s",
			assign.GetComponentType().String(),
			target.componentType.String(),
		)
	}
	if strings.TrimSpace(assign.GetTidbGroupId()) != strings.TrimSpace(target.tidbGroupID) {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous gatekeeper assign tidb group mismatch: got %s, want %s",
			assign.GetTidbGroupId(),
			target.tidbGroupID,
		)
	}
	if target.tidbGroupKind != regionaldedicatedv1.TiDBGroupKind_TIDB_GROUP_KIND_UNSPECIFIED &&
		assign.GetTidbGroupKind() != target.tidbGroupKind {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous gatekeeper assign tidb group kind mismatch: got %s, want %s",
			assign.GetTidbGroupKind().String(),
			target.tidbGroupKind.String(),
		)
	}
	computeClassName := strings.TrimSpace(deploy.GetComputeClassName())
	if computeClassName == "" {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "empty compute class name in gke heterogeneous deploy")
	}
	if strings.TrimSpace(assign.GetComputeClassName()) != computeClassName {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous gatekeeper assign compute class mismatch: got %s, want %s",
			assign.GetComputeClassName(),
			computeClassName,
		)
	}
	return nil
}
