package user

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"strconv"
	"strings"

	"braces.dev/errtrace"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/serverlessglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/serverless"
)

type Service interface {
	ChangeSqlUserPassword(ctx context.Context, clusterId, user, password string) (*entity.ClusterCustomStatus, error)
	RotateRootPassword(ctx context.Context, clusterId string) (*globalserverlessv1.RotateRootPasswordResponse, *entity.ClusterCustomStatus, error)
	RedeemRootPassword(ctx context.Context, clusterId string) (*globalserverlessv1.RedeemRootPasswordResponse, error)
}

type service struct {
	clusterRepo      repository.ClusterRepository
	serverlessCli    serverlessglobal.ServerlessGlobal
	customStatusRepo repository.CustomStatusRepository
	transaction      db.TransProvider
}

func NewService(
	clusterRepo repository.ClusterRepository,
	customStatusRepo repository.CustomStatusRepository,
	serverlessCli serverlessglobal.ServerlessGlobal,
	transaction db.TransProvider,
) Service {
	return &service{
		clusterRepo:      clusterRepo,
		customStatusRepo: customStatusRepo,
		serverlessCli:    serverlessCli,
		transaction:      transaction,
	}
}

func (svc *service) ChangeSqlUserPassword(
	ctx context.Context,
	clusterId, user, password string,
) (*entity.ClusterCustomStatus, error) {
	ctx, span := serverless.Tracer.Start(ctx, "UserService.ChangeSqlUserPassword")
	defer span.End()

	var status *entity.ClusterCustomStatus
	if err := svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		var (
			err     error
			isMutex bool
		)
		// check ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD mutex
		clusterIdUint64, err := strconv.ParseUint(clusterId, 10, 64)
		if err != nil {
			return fmt.Errorf("failed to parse cluster id: %w", err)
		}

		isMutex, status, err = svc.customStatusRepo.IsMutexClusterAction(
			ctx,
			clusterIdUint64,
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD,
		)
		if err != nil {
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return fmt.Errorf("failed to check mutex cluster action: %w", err)
			}
		}

		if isMutex {
			tilog.Error(ctx, "mutex custom status is exist", zap.String("cluster_id", clusterId), zap.Any("custom status", status))
			return errtrace.Wrap(constant.ErrClusterCustomStatusForbidden)
		}

		_, err = svc.serverlessCli.ChangeSqlUserPasswd(ctx, &globalserverlessv1.ChangeSqlUserPasswdRequest{
			ClusterId: clusterId,
			UserName:  user,
			Passwd:    password,
		})
		if err != nil {
			return errtrace.Wrap(err)
		}
		return nil
	}); err != nil {
		return status, errtrace.Wrap(err)
	}

	return &entity.ClusterCustomStatus{}, nil
}

func (svc *service) RotateRootPassword(
	ctx context.Context,
	clusterId string,
) (*globalserverlessv1.RotateRootPasswordResponse, *entity.ClusterCustomStatus, error) {
	ctx, span := serverless.Tracer.Start(ctx, "UserService.RotateRootPassword")
	defer span.End()

	clusterIdUint64, err := strconv.ParseUint(clusterId, 10, 64)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse cluster id: %w", err)
	}

	var (
		status *entity.ClusterCustomStatus
		resp   *globalserverlessv1.RotateRootPasswordResponse
	)

	if err = svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		cluster, getErr := svc.clusterRepo.GetCluster(ctx, clusterIdUint64, false)
		if getErr != nil {
			return getErr
		}
		if cluster.DeployType != entity.DeployTypeServerless {
			return errors.New("cluster deploy type is not serverless")
		}

		isMutex, mutexStatus, mutexErr := svc.customStatusRepo.IsMutexClusterAction(
			ctx,
			clusterIdUint64,
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD,
		)
		if mutexErr != nil && !errors.Is(mutexErr, gorm.ErrRecordNotFound) {
			return fmt.Errorf("failed to check mutex cluster action: %w", mutexErr)
		}
		if isMutex {
			status = mutexStatus
			tilog.Error(ctx, "mutex custom status is exist", zap.String("cluster_id", clusterId), zap.Any("custom status", status))
			return errtrace.Wrap(constant.ErrClusterCustomStatusForbidden)
		}

		statusID := entity.GenStatusId()
		status = &entity.ClusterCustomStatus{
			StatusId:      statusID,
			ClusterId:     clusterIdUint64,
			StatusName:    fmt.Sprintf("rotate-root-password-%s-%d", clusterId, statusID),
			StatusType:    tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_MODIFYING,
			OverrideState: openapicommonv1beta1.ClusterState_MODIFYING.Enum(),
			CustomMutex: &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
				ClusterAction: []tidbmgmtservicev1.ClusterAction{
					tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD,
					tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_MODIFY_CLUSTER,
				},
				StatusType: []tidbmgmtservicev1.CustomStatusType{
					tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_MODIFYING,
				},
			},
		}
		if _, newErr := svc.customStatusRepo.NewClusterCustomStatus(ctx, status); newErr != nil {
			return fmt.Errorf("failed to set cluster custom status: %w", newErr)
		}

		resp, err = svc.serverlessCli.RotateRootPassword(ctx, &globalserverlessv1.RotateRootPasswordRequest{ClusterId: clusterId})
		if err != nil {
			return errtrace.Wrap(err)
		}
		if resp != nil && !resp.GetRotating() {
			if delErr := svc.customStatusRepo.DeleteClusterCustomStatus(ctx, status.StatusId); delErr != nil {
				tilog.Error(ctx, "delete root-password custom status failed", zap.Uint64("status_id", status.StatusId), zap.Error(delErr))
			}
		}
		return nil
	}); err != nil {
		return nil, status, errtrace.Wrap(err)
	}

	return resp, status, nil
}

func (svc *service) RedeemRootPassword(ctx context.Context, clusterId string) (*globalserverlessv1.RedeemRootPasswordResponse, error) {
	ctx, span := serverless.Tracer.Start(ctx, "UserService.RedeemRootPassword")
	defer span.End()

	resp, err := svc.serverlessCli.RedeemRootPassword(ctx, &globalserverlessv1.RedeemRootPasswordRequest{ClusterId: clusterId})
	if err != nil {
		return nil, errtrace.Wrap(err)
	}
	if resp == nil || resp.GetRotating() {
		return resp, nil
	}

	clusterIdUint64, parseErr := strconv.ParseUint(clusterId, 10, 64)
	if parseErr != nil {
		return resp, nil
	}

	statusMap, listErr := svc.customStatusRepo.ListClusterCustomStatus(
		ctx,
		[]uint64{clusterIdUint64},
		[]tidbmgmtservicev1.CustomStatusType{tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_MODIFYING},
		false,
	)
	if listErr != nil {
		return resp, nil
	}
	for _, st := range statusMap[clusterIdUint64] {
		if st == nil || !strings.HasPrefix(st.StatusName, "rotate-root-password-") {
			continue
		}
		if st.CustomMutex == nil || !slices.Contains(st.CustomMutex.ClusterAction, tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD) {
			continue
		}
		_ = svc.customStatusRepo.DeleteClusterCustomStatus(ctx, st.StatusId)
	}

	return resp, nil
}
