package repository

import (
	"context"
	"errors"
	"math"
	"time"

	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	domainrepo "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type DedicatedToPremiumPrecheckRepoImpl struct {
	dbProvider db.Provider
}

func NewDedicatedToPremiumPrecheckRepo(dbProvider db.Provider) domainrepo.DedicatedToPremiumPrecheckRepo {
	return &DedicatedToPremiumPrecheckRepoImpl{dbProvider: dbProvider}
}

func (r *DedicatedToPremiumPrecheckRepoImpl) Create(
	ctx context.Context,
	precheck *entity.DedicatedToPremiumPrecheck,
) error {
	if precheck == nil {
		return bizerr.New(bizerr.ErrCodeParams, "precheck is nil")
	}
	model := precheckToModel(precheck)
	wdb := r.dbProvider.WriteDB(ctx)
	if err := wdb.Create(model).Error; err != nil {
		return bizerr.WrapGormError(err, "create dedicated to premium precheck failed")
	}
	precheck.ID = uint64(model.ID)
	models := make([]*schema.DedicatedToPremiumPrecheckItem, 0, len(precheck.Items))
	for _, item := range precheck.Items {
		item.PrecheckID = precheck.ID
		models = append(models, precheckItemToModel(item))
	}
	if len(models) > 0 {
		if err := wdb.Create(&models).Error; err != nil {
			return bizerr.WrapGormError(err, "create dedicated to premium precheck items failed")
		}
		for i := range models {
			precheck.Items[i].ID = uint64(models[i].ID)
		}
	}
	return nil
}

func (r *DedicatedToPremiumPrecheckRepoImpl) Get(
	ctx context.Context,
	id uint64,
) (*entity.DedicatedToPremiumPrecheck, error) {
	var model schema.DedicatedToPremiumPrecheck
	if err := r.dbProvider.ReadDB(ctx).Where("id = ?", id).First(&model).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "dedicated to premium precheck not found")
		}
		return nil, bizerr.WrapGormError(err, "get dedicated to premium precheck failed")
	}
	var itemModels []*schema.DedicatedToPremiumPrecheckItem
	if err := r.dbProvider.ReadDB(ctx).
		Where("precheck_id = ?", id).
		Order("category ASC, position ASC").
		Find(&itemModels).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "list dedicated to premium precheck items failed")
	}
	precheck := precheckFromModel(&model)
	precheck.Items = make([]*entity.DedicatedToPremiumPrecheckItem, 0, len(itemModels))
	for _, itemModel := range itemModels {
		precheck.Items = append(precheck.Items, precheckItemFromModel(itemModel))
	}
	return precheck, nil
}

func (r *DedicatedToPremiumPrecheckRepoImpl) Count(
	ctx context.Context,
	filter *domainrepo.DedicatedToPremiumPrecheckFilter,
) (uint32, error) {
	query := r.dbProvider.ReadDB(ctx).Model(&schema.DedicatedToPremiumPrecheck{})
	if filter != nil && filter.SourceClusterID != 0 {
		query = query.Where("source_cluster_id = ?", filter.SourceClusterID)
	}
	var count int64
	if err := query.Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "count dedicated to premium prechecks failed")
	}
	if count > math.MaxUint32 {
		return 0, bizerr.WrapError(errors.New("count exceeds uint32"), "count dedicated to premium prechecks failed")
	}
	return uint32(count), nil
}

func (r *DedicatedToPremiumPrecheckRepoImpl) List(
	ctx context.Context,
	filter *domainrepo.DedicatedToPremiumPrecheckFilter,
) ([]*entity.DedicatedToPremiumPrecheck, error) {
	if filter == nil {
		filter = &domainrepo.DedicatedToPremiumPrecheckFilter{}
	}
	query := r.dbProvider.ReadDB(ctx).Model(&schema.DedicatedToPremiumPrecheck{})
	if filter.SourceClusterID != 0 {
		query = query.Where("source_cluster_id = ?", filter.SourceClusterID)
	}
	if filter.Limit > 0 {
		query = query.Limit(filter.Limit)
	}
	if filter.Offset > 0 {
		query = query.Offset(filter.Offset)
	}
	var models []*schema.DedicatedToPremiumPrecheck
	if err := query.Order("id DESC").Find(&models).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "list dedicated to premium prechecks failed")
	}
	if len(models) == 0 {
		return []*entity.DedicatedToPremiumPrecheck{}, nil
	}

	precheckIDs := make([]uint64, 0, len(models))
	for _, model := range models {
		precheckIDs = append(precheckIDs, uint64(model.ID))
	}
	var itemModels []*schema.DedicatedToPremiumPrecheckItem
	if err := r.dbProvider.ReadDB(ctx).
		Where("precheck_id IN ?", precheckIDs).
		Order("precheck_id DESC, category ASC, position ASC").
		Find(&itemModels).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "list dedicated to premium precheck items failed")
	}
	itemsByPrecheckID := make(map[uint64][]*entity.DedicatedToPremiumPrecheckItem, len(models))
	for _, itemModel := range itemModels {
		itemsByPrecheckID[itemModel.PrecheckID] = append(
			itemsByPrecheckID[itemModel.PrecheckID],
			precheckItemFromModel(itemModel),
		)
	}
	prechecks := make([]*entity.DedicatedToPremiumPrecheck, 0, len(models))
	for _, model := range models {
		precheck := precheckFromModel(model)
		precheck.Items = itemsByPrecheckID[precheck.ID]
		if precheck.Items == nil {
			precheck.Items = []*entity.DedicatedToPremiumPrecheckItem{}
		}
		prechecks = append(prechecks, precheck)
	}
	return prechecks, nil
}

func (r *DedicatedToPremiumPrecheckRepoImpl) GetItem(
	ctx context.Context,
	precheckID uint64,
	category entity.PrecheckCategory,
	checkKey string,
) (*entity.DedicatedToPremiumPrecheckItem, error) {
	var model schema.DedicatedToPremiumPrecheckItem
	err := r.dbProvider.ReadDB(ctx).
		Where("precheck_id = ? AND category = ? AND check_key = ?", precheckID, string(category), checkKey).
		First(&model).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "precheck item not found")
		}
		return nil, bizerr.WrapGormError(err, "get precheck item failed")
	}
	return precheckItemFromModel(&model), nil
}

func (r *DedicatedToPremiumPrecheckRepoImpl) UpdateItem(
	ctx context.Context,
	item *entity.DedicatedToPremiumPrecheckItem,
) error {
	if item == nil || item.ID == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "precheck item id is required")
	}
	model := precheckItemToModel(item)
	model.ID = uint(item.ID)
	return bizerr.WrapGormError(
		r.dbProvider.WriteDB(ctx).Omit("created_at").Save(model).Error,
		"update precheck item failed",
	)
}

func (r *DedicatedToPremiumPrecheckRepoImpl) MarkOperationRunning(
	ctx context.Context,
	precheckID uint64,
	allowFromFailed bool,
) error {
	if precheckID == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "precheck id is required")
	}
	runnableStates := []entity.DedicatedToPremiumPrecheckState{
		entity.DedicatedToPremiumPrecheckStatePending,
		entity.DedicatedToPremiumPrecheckStateRunning,
	}
	if allowFromFailed {
		runnableStates = append(runnableStates, entity.DedicatedToPremiumPrecheckStateFailed)
	}
	result := r.dbProvider.WriteDB(ctx).Model(&schema.DedicatedToPremiumPrecheck{}).
		Where("id = ? AND state IN ?", precheckID, runnableStates).
		Updates(map[string]any{
			"state": entity.DedicatedToPremiumPrecheckStateRunning, "failure_message": "", "finished_at": nil,
			"started_at": gorm.Expr("COALESCE(started_at, ?)", time.Now()),
		})
	if result.Error != nil {
		return bizerr.WrapGormError(result.Error, "mark dedicated to premium precheck running failed")
	}
	if result.RowsAffected > 0 {
		return nil
	}

	var operation schema.DedicatedToPremiumPrecheck
	if err := r.dbProvider.ReadDB(ctx).Select("state").Where("id = ?", precheckID).First(&operation).Error; err != nil {
		return bizerr.WrapGormError(err, "get dedicated to premium precheck state failed")
	}
	if operation.State == string(entity.DedicatedToPremiumPrecheckStateRunning) {
		return nil
	}
	return bizerr.New(bizerr.ErrCodeGeneralBusiness, "precheck operation is not runnable from state %s", operation.State)
}

func (r *DedicatedToPremiumPrecheckRepoImpl) UpdateOperation(
	ctx context.Context,
	precheck *entity.DedicatedToPremiumPrecheck,
) error {
	if precheck == nil || precheck.ID == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "precheck id is required")
	}
	updates := map[string]any{
		"state": precheck.State, "failure_message": precheck.FailureMessage, "finished_at": precheck.FinishedAt,
	}
	return bizerr.WrapGormError(
		r.dbProvider.WriteDB(ctx).Model(&schema.DedicatedToPremiumPrecheck{}).
			Where("id = ?", precheck.ID).Updates(updates).Error,
		"update dedicated to premium precheck failed",
	)
}

func precheckToModel(value *entity.DedicatedToPremiumPrecheck) *schema.DedicatedToPremiumPrecheck {
	return &schema.DedicatedToPremiumPrecheck{
		SourceClusterID: value.SourceClusterID, MigrationAccountEmail: value.MigrationAccountEmail,
		TargetPremiumProjectID: value.TargetPremiumProjectID, Applicant: value.Applicant, State: string(value.State),
		FailureMessage: value.FailureMessage, StartedAt: value.StartedAt, FinishedAt: value.FinishedAt,
	}
}

func precheckFromModel(value *schema.DedicatedToPremiumPrecheck) *entity.DedicatedToPremiumPrecheck {
	return &entity.DedicatedToPremiumPrecheck{
		ID: uint64(value.ID), SourceClusterID: value.SourceClusterID,
		MigrationAccountEmail: value.MigrationAccountEmail, TargetPremiumProjectID: value.TargetPremiumProjectID,
		Applicant: value.Applicant,
		State:     entity.DedicatedToPremiumPrecheckState(value.State), FailureMessage: value.FailureMessage,
		CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, StartedAt: value.StartedAt, FinishedAt: value.FinishedAt,
	}
}

func precheckItemToModel(value *entity.DedicatedToPremiumPrecheckItem) *schema.DedicatedToPremiumPrecheckItem {
	return &schema.DedicatedToPremiumPrecheckItem{
		PrecheckID: value.PrecheckID, Category: string(value.Category), CheckKey: value.CheckKey,
		Title: value.Title, Position: value.Position, Status: string(value.Status), Why: value.Why,
		Evidence: value.Evidence, Action: value.Action, ExecutionState: string(value.ExecutionState),
		ExecutionError: value.ExecutionError,
	}
}

func precheckItemFromModel(value *schema.DedicatedToPremiumPrecheckItem) *entity.DedicatedToPremiumPrecheckItem {
	return &entity.DedicatedToPremiumPrecheckItem{
		ID: uint64(value.ID), PrecheckID: value.PrecheckID, Category: entity.PrecheckCategory(value.Category),
		CheckKey: value.CheckKey, Title: value.Title, Position: value.Position,
		Status: entity.PrecheckItemStatus(value.Status), Why: value.Why, Evidence: value.Evidence,
		Action: value.Action, ExecutionState: entity.PrecheckItemExecutionState(value.ExecutionState),
		ExecutionError: value.ExecutionError, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt,
	}
}
