package cluster

import (
	"context"
	"strconv"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"golang.org/x/mod/semver"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"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/clusterevent"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *serviceImpl) ShutdownUpgradeCluster(ctx context.Context, req *globaldedicatedv1.ShutdownUpgradeClusterRequest) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "Domain.ShutdownUpgradeCluster")
	defer span.End()

	// Validate request
	if req.ClusterId == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "cluster_id is required")
	}
	if req.TargetVersion == "" {
		return bizerr.New(bizerr.ErrCodeParams, "target_version is required")
	}

	var cluster *entity.Cluster
	err := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		var err error
		cluster, err = s.clusterRepo.Get(ctx, req.ClusterId, false)
		if err != nil {
			return err
		}

		// Validation: Check cluster state
		if cluster.State != globaldedicatedv1.Cluster_AVAILABLE {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"cluster %d state is %s, cannot shutdown upgrade",
				cluster.ClusterId,
				cluster.State.String(),
			)
		}

		// Validation: Check if cluster is paused
		if cluster.State == globaldedicatedv1.Cluster_PAUSED {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"cluster %d is paused, cannot shutdown upgrade",
				cluster.ClusterId,
			)
		}

		// Validation: Parse and validate current version
		if !semver.IsValid(cluster.Version) {
			return bizerr.New(bizerr.ErrCodeParams, "cluster %d has invalid current version %s", cluster.ClusterId, cluster.Version)
		}

		// Validation: Parse and validate target version
		if !semver.IsValid(req.TargetVersion) {
			return bizerr.New(bizerr.ErrCodeParams, "invalid target version format %s", req.TargetVersion)
		}

		// Validation: Target version must be greater than current version
		if semver.Compare(req.TargetVersion, cluster.Version) <= 0 {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"cluster %d version is %s, cannot shutdown upgrade to %s (target version must be higher)",
				cluster.ClusterId,
				cluster.Version,
				req.TargetVersion,
			)
		}
		if req.GetValidateOnly() {
			tilog.Info(ctx, "shutdown upgrade request validated",
				zap.Uint64("cluster_id", cluster.ClusterId),
				zap.String("current_version", cluster.Version),
				zap.String("target_version", req.TargetVersion),
				zap.Bool("validate_only", req.ValidateOnly))
			return nil
		}

		tilog.Info(ctx, "starting shutdown upgrade",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.String("current_version", cluster.Version),
			zap.String("target_version", req.TargetVersion))

		oldVersion := cluster.Version

		// Set cluster state to MODIFYING
		cluster.State = globaldedicatedv1.Cluster_MODIFYING
		cluster.HealthState = globaldedicatedv1.Cluster_HEALTHY

		// Save cluster state and version
		if err := s.clusterRepo.Save(ctx, cluster); err != nil {
			return err
		}

		// Create shutdown upgrade event
		shutdownUpgradeEvent := entity.NewClusterEventBuilder(ctx, cluster, clusterevent.EventTypeShutdownUpgrade).
			SetOldVersion(oldVersion).
			SetNewVersion(req.TargetVersion).
			Build()
		if err := s.eventCommitter.Write(ctx, clusterevent.TopicClusterEvent, shutdownUpgradeEvent); err != nil {
			return err
		}

		// Marshal request metadata
		metadataJSON, mErr := metadata.MarshalRequestMetaFromCtx(ctx)
		if mErr != nil {
			tilog.Warn(ctx, "failed to marshal request metadata", zap.Error(mErr))
		}

		// Prepare workflow variables
		clusterIdStr := strconv.FormatUint(cluster.ClusterId, 10)
		orgIdStr := strconv.FormatUint(cluster.OrganizationId, 10)
		projectIdStr := strconv.FormatUint(cluster.ProjectId, 10)

		// Start shutdown upgrade workflow DAG
		if wErr := s.workflow.RunDagWithBizId(
			ctx,
			common.ShutdownUpgradeCluster,
			orgIdStr,
			projectIdStr,
			clusterIdStr,
			map[string]string{
				workflow.KeyTraceId:       workflow.GetTraceId(ctx),
				workflow.KeyRequestMeta:   string(metadataJSON),
				common.KeyClusterId:       clusterIdStr,
				common.KeyTargetVersion:   req.TargetVersion,
				common.KeyTidbImageTag:    req.TidbImageTag,
				common.KeyTikvImageTag:    req.TikvImageTag,
				common.KeyPdImageTag:      req.PdImageTag,
				common.KeyTiflashImageTag: req.TiflashImageTag,
				common.KeyTargetStatus:    globaldedicatedv1.Cluster_AVAILABLE.String(),
			},
		); wErr != nil {
			return bizerr.WrapErrorWithCode(wErr, bizerr.ErrCodeUnknown, "create shutdown upgrade workflow failed")
		}

		tilog.Info(ctx, "shutdown upgrade workflow started",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.String("workflow", common.ShutdownUpgradeCluster))

		return nil
	})
	if err != nil {
		return bizerr.WrapError(err, "shutdown upgrade cluster failed")
	}

	return nil
}
