package cluster

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/serverless"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

// RentCluster rents a prepared cluster from a shadow pool.
// This method only handles state transitions and does not implement
// other business logic like renaming or password resets.
// The targetProjectID parameter is reserved for future use when implementing
// project transfer logic, but is not used in the current MVP implementation.
func (svc *service) RentCluster(
	ctx context.Context,
	shadowPoolID uint64,
	targetProjectID uint64,
	namePrefix string,
	passwd string,
	sourceIP string,
	tag string,
) (string, *entity.Cluster, error) {
	spanName := "ClusterService.RentCluster"
	ctx, span := serverless.Tracer.Start(ctx, spanName)
	defer span.End()

	var rentedCluster *entity.Cluster
	var zeroId string

	// Execute rent in a transaction
	if err := svc.transaction.Transaction(ctx, func(txCtx context.Context) error {
		// Query prepared clusters in the shadow pool
		clusters, err := svc.clusterRepo.QueryPreparedShadowClustersForUpdate(txCtx, shadowPoolID, 1)
		if err != nil {
			return bizerr.WrapGormError(err, "failed to query clusters from shadow pool")
		}

		// Find a prepared cluster in the shadow pool
		var preparedCluster *entity.Cluster
		for _, cluster := range clusters {
			if cluster.ShadowPoolId == shadowPoolID && cluster.ShadowState == entity.ShadowStatePrepared {
				preparedCluster = cluster
				break
			}
		}

		if preparedCluster == nil {
			return bizerr.New(
				bizerr.ErrCodeRecordNotFound,
				"no prepared cluster available in shadow pool %d",
				shadowPoolID,
			)
		}

		tilog.Info(txCtx, "Renting cluster from shadow pool",
			zap.Uint64("pool_id", shadowPoolID),
			zap.Uint64("cluster_id", preparedCluster.ClusterId),
			zap.Uint64("target_project_id", targetProjectID))

		if updateErr := svc.clusterRepo.UpdateShadowState(txCtx,
			preparedCluster.ClusterId,
			entity.ShadowStateRenting,
			entity.ShadowStatePrepared); updateErr != nil {
			return bizerr.WrapGormError(updateErr, "failed to update cluster shadow state to Renting during rent")
		}

		zeroId, err = svc.buildZeroId(txCtx, preparedCluster.ClusterId, sourceIP, tag)
		if err != nil {
			tilog.Error(txCtx, "failed to build zero id for rented cluster",
				zap.Uint64("cluster_id", preparedCluster.ClusterId),
				zap.Error(err))
		}

		if zeroId == "" {
			return bizerr.New(
				bizerr.GeneralInternalErrorCode,
				"failed to build zero id for cluster %d in shadow pool %d",
				preparedCluster.ClusterId,
				shadowPoolID,
			)
		}

		if namePrefix != "" {
			preparedCluster.DisplayName = fmt.Sprintf("%s-%d-%d", namePrefix, shadowPoolID, preparedCluster.ClusterId)
			err = svc.clusterRepo.UpdateDisplayName(txCtx, preparedCluster.ClusterId, preparedCluster.DisplayName)
			if err != nil {
				return bizerr.WrapGormError(err, "failed to update cluster display name during rent")
			}
		}

		_, err = svc.serverlessCli.ChangeSqlUserPasswd(txCtx, &globalserverlessv1.ChangeSqlUserPasswdRequest{
			ClusterId: strconv.FormatUint(preparedCluster.ClusterId, 10),
			UserName:  "root",
			Passwd:    passwd,
		})

		if err != nil {
			return bizerr.WrapError(err, "failed to reset password for rented cluster")
		}

		if updateErr := svc.clusterRepo.UpdateShadowState(txCtx,
			preparedCluster.ClusterId,
			entity.ShadowStateRented,
			entity.ShadowStateRenting); updateErr != nil {
			return bizerr.WrapGormError(updateErr, "failed to update cluster shadow state to rented during rent")
		}

		preparedCluster.ShadowState = entity.ShadowStateRented
		rentedCluster = preparedCluster
		return nil
	}); err != nil {
		return "", nil, err
	}

	cluster, err := svc.GetCluster(ctx, rentedCluster.ClusterId, tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_FULL)
	if err != nil {
		return "", nil, bizerr.WrapError(err, "failed to get rented cluster details")
	}

	tilog.Info(ctx, "Successfully rented cluster from shadow pool",
		zap.Uint64("pool_id", shadowPoolID),
		zap.Uint64("cluster_id", rentedCluster.ClusterId))

	return zeroId, cluster, nil
}
