package quota

import (
	"context"

	"github.com/samber/lo"
	"github.com/tidbcloud/pkgv2/db"

	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/repository/quota"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type tenantQuotaRepoImpl struct {
	dbProvider db.Provider
	quota      *config.ClusterQuota
}

func NewTenantQuotaRepo(p db.Provider, cfg *config.Config) quota.TenantClusterQuotaRepo {
	return &tenantQuotaRepoImpl{
		dbProvider: p,
		quota:      &cfg.BizConfig.ClusterQuota,
	}
}

func (r *tenantQuotaRepoImpl) Load(ctx context.Context, tenantID uint64) (*entity.TenantClusterQuota, bizerr.BizError) {
	rdb := r.dbProvider.ReadDB(ctx)
	var quotas []*schema.Quota

	if err := rdb.Where("tenant_id = ?", tenantID).Find(&quotas).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load tenant quota")
	}

	return TenantQuotaModelToEn(tenantID, quotas, r.quota)
}

func (r *tenantQuotaRepoImpl) Max4CTikvCount(ctx context.Context) int {
	if r.quota.Max4CTikvCount == 0 {
		return 3
	}

	return r.quota.Max4CTikvCount
}

type tenantClusterComponentQuotaRepoImpl struct {
	db.Provider

	quota *config.ClusterQuota
}

func NewTenantClusterComponentQuotaRepo(p db.Provider, cfg *config.Config) quota.TenantClusterComponentQuotaRepo {
	return &tenantClusterComponentQuotaRepoImpl{
		Provider: p,
		quota:    &cfg.BizConfig.ClusterQuota,
	}
}

func (r *tenantClusterComponentQuotaRepoImpl) Query(
	ctx context.Context,
	filter quota.TenantQuotaFilter,
) ([]*entity.TenantComponentQuota, bizerr.BizError) {
	rdb := r.ReadDB(ctx)
	var quotas []*schema.Quota

	rdb = rdb.Where("tenant_id = ?", filter.TenantID)

	if filter.Component != globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		rdb = rdb.Where("component_name = ?", filter.Component.String())
	}

	if err := rdb.Find(&quotas).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load tenant quota")
	}

	var out []*entity.TenantComponentQuota
	componentDefaultQuota := []*entity.TenantComponentQuota{
		{
			ID:            0,
			TenantID:      filter.TenantID,
			ComponentName: globaldedicatedv1.ComponentType_TIDB,
			Quota:         uint32(r.quota.TiDB),
			CreateTime:    nil,
		},
		{
			ID:            0,
			TenantID:      filter.TenantID,
			ComponentName: globaldedicatedv1.ComponentType_TIKV,
			Quota:         uint32(r.quota.TiKV),
			CreateTime:    nil,
		},
		{
			ID:            0,
			TenantID:      filter.TenantID,
			ComponentName: globaldedicatedv1.ComponentType_TIFLASH,
			Quota:         uint32(r.quota.TiFlash),
			CreateTime:    nil,
		},
		{
			ID:            0,
			TenantID:      filter.TenantID,
			ComponentName: globaldedicatedv1.ComponentType_TIPROXY,
			Quota:         uint32(r.quota.TiProxy),
			CreateTime:    nil,
		},
	}
	if filter.Component != globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
		out = lo.Filter(componentDefaultQuota, func(item *entity.TenantComponentQuota, _ int) bool {
			return item.ComponentName == filter.Component
		})
	} else {
		out = componentDefaultQuota
	}

	for i, item := range out {
		for _, q := range quotas {
			en, err := tenantComponentQuotaModel2En(q)
			if err != nil {
				return nil, bizerr.WrapError(err, "convert quota")
			}
			if en.ComponentName == item.ComponentName {
				out[i] = en
				break
			}
		}
	}

	return out, nil
}

func (r *tenantClusterComponentQuotaRepoImpl) Save(
	ctx context.Context,
	en *entity.TenantComponentQuota,
) bizerr.BizError {
	wdb := r.WriteDB(ctx)
	q := tenantComponentQuotaEnToModel(en)
	if err := wdb.Save(q).Error; err != nil {
		return bizerr.WrapGormError(err, "save tenant quota")
	}

	return nil
}
