package tidbgroup

import (
	"context"
	"fmt"

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

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

type tidbNodeGroupImp struct {
	dbProvider db.Provider
}

func NewTidbGroupRepo(dbProvider db.Provider) tidbgroup.Repo {
	return &tidbNodeGroupImp{
		dbProvider: dbProvider,
	}
}

func buildWorkPoolGetter(tx *gorm.DB) *gorm.DB {
	return tx.Model(&schema.WorkPool{})
}

func buildTxByWorkPoolFilter(tx *gorm.DB, filter *tidbgroup.Filter) *gorm.DB {
	if len(filter.Ids) > 0 {
		tx = tx.Where("`id` IN ?", filter.Ids)
	}
	if len(filter.ClusterIds) > 0 {
		tx = tx.Where("`cluster_id` IN ?", filter.ClusterIds)
	}
	if len(filter.DisplayNames) > 0 {
		tx = tx.Where("`display_name` IN ?", filter.DisplayNames)
	}
	if filter.ShowDeleted {
		tx = tx.Unscoped()
	}
	return tx
}

func (p *tidbNodeGroupImp) Load(ctx context.Context, clusterID, id uint64) (*entity.TidbNodeGroup, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)

	var workPool schema.WorkPool
	if err := buildWorkPoolGetter(rdb).Where("`cluster_id` = ? AND `id` = ?", clusterID, id).First(&workPool).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load tidb node group")
	}

	return modelToEn(&workPool), nil
}

func (p *tidbNodeGroupImp) LoadForUpdate(
	ctx context.Context,
	clusterID,
	id uint64,
) (*entity.TidbNodeGroup, bizerr.BizError) {
	wdb := p.dbProvider.WriteDB(ctx)

	var workPool schema.WorkPool
	if err := buildWorkPoolGetter(wdb).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("`cluster_id` = ? AND `id` = ?", clusterID, id).
		First(&workPool).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load tidb node group for update")
	}

	return modelToEn(&workPool), nil
}

func (p *tidbNodeGroupImp) LoadDefault(ctx context.Context, clusterID uint64) (*entity.TidbNodeGroup, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)

	var workPool schema.WorkPool
	if err := buildWorkPoolGetter(rdb).Where("`cluster_id` = ? AND `default` = ?", clusterID, true).First(&workPool).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load default accesspoint")
	}

	return modelToEn(&workPool), nil
}

func (p *tidbNodeGroupImp) Query(
	ctx context.Context,
	filter *tidbgroup.Filter,
) ([]*entity.TidbNodeGroup, bizerr.BizError) {
	if len(filter.ClusterIds) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "ClusterIDs filter is required")
	}

	rdb := p.dbProvider.ReadDB(ctx)
	var workPools []*schema.WorkPool
	tx := buildTxByWorkPoolFilter(buildWorkPoolGetter(rdb), filter)

	if err := tx.Find(&workPools).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "query work pool")
	}
	if len(workPools) == 0 {
		return nil, nil
	}

	var result []*entity.TidbNodeGroup
	for _, workPool := range workPools {
		result = append(result, modelToEn(workPool))
	}
	return result, nil
}

func (p *tidbNodeGroupImp) Count(ctx context.Context, filter *tidbgroup.Filter) (int64, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)
	var count int64
	tx := buildTxByWorkPoolFilter(buildWorkPoolGetter(rdb), filter)

	if err := tx.Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "count work pool")
	}
	return count, nil
}

func (p *tidbNodeGroupImp) Create(ctx context.Context, workPool *entity.TidbNodeGroup) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)

	workPoolModel := enToModel(workPool)
	err := wdb.Create(workPoolModel).Error
	if err != nil {
		return bizerr.WrapGormError(err, "create work pool")
	}

	return nil
}

func (p *tidbNodeGroupImp) Save(ctx context.Context, workPool *entity.TidbNodeGroup) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)

	workPoolModel := enToModel(workPool)
	err := wdb.Omit("created_at").Save(workPoolModel).Error
	if err != nil {
		return bizerr.WrapGormError(err, "save work pool")
	}

	return nil
}

func (p *tidbNodeGroupImp) DeleteByClusterId(ctx context.Context, clusterId uint64) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)

	if err := wdb.Where("`cluster_id` = ?", clusterId).Delete(&schema.WorkPool{}).Error; err != nil {
		return bizerr.WrapGormError(err, "delete work pool")
	}
	return nil
}

func (p *tidbNodeGroupImp) Delete(ctx context.Context, tidbGroup *entity.TidbNodeGroup) bizerr.BizError {
	if err := metadata.Validate(ctx, &metadata.ExpectRequest[uint64]{
		ClusterId:  tidbGroup.ClusterId,
		ResourceId: fmt.Sprintf("%d", tidbGroup.Id),
	}); err != nil {
		return bizerr.WrapError(err, "validate failed")
	}

	wdb := p.dbProvider.WriteDB(ctx)

	if err := wdb.Where("`id` = ? and cluster_id = ?", tidbGroup.Id, tidbGroup.ClusterId).Delete(&schema.WorkPool{}).Error; err != nil {
		return bizerr.WrapGormError(err, "delete work pool")
	}
	return nil
}

func (p *tidbNodeGroupImp) LoadDefaultByClusterIds(
	ctx context.Context,
	clusterIds []uint64,
	showDeleted bool,
) (map[uint64]*entity.TidbNodeGroup, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)

	var workPools []*schema.WorkPool
	tx := buildTxByWorkPoolFilter(
		buildWorkPoolGetter(rdb),
		&tidbgroup.Filter{ClusterIds: clusterIds, ShowDeleted: showDeleted},
	).
		Where("`default` = ?", true)

	if err := tx.Find(&workPools).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "query tidb node group")
	}

	result := make(map[uint64]*entity.TidbNodeGroup)
	for _, workPool := range workPools {
		result[workPool.ClusterId] = modelToEn(workPool)
	}

	return result, nil
}
