package metering

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/db"

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

type clusterMeteringRepoImp struct {
	dbProvider db.Provider
}

func NewClusterMeteringRepo(dbProvider db.Provider) billingrepo.ClusterMeteringRepo {
	return clusterMeteringRepoImp{
		dbProvider: dbProvider,
	}
}

func (p clusterMeteringRepoImp) LoadAndLockLatestOne(
	ctx context.Context,
	clusterID uint64,
) (*entity.ClusterMetering, bizerr.BizError) {
	wdb := p.dbProvider.WriteDB(ctx)
	metering := &schema.ClusterMetering{}
	if err := wdb.Where("cluster_id=?", clusterID).
		Order("start_ts desc").First(metering).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find first cluster metering")
	}
	// try lock
	updateResult := wdb.Model(&schema.ClusterMetering{}).Where("id=? and latest=?", metering.ID, 1).Update("latest", 0)
	if updateResult.Error != nil {
		return nil, bizerr.WrapGormError(updateResult.Error, "update latest cluster metering failed")
	}
	if updateResult.RowsAffected != 1 {
		return nil, bizerr.New(
			bizerr.ErrCodeOptimisticLockConflict,
			"update latest cluster metering conflict: affected=%d",
			updateResult.RowsAffected,
		)
	}

	en, err := ClusterMeteringModelToEn(metering)
	if err != nil {
		return nil, err
	}
	return en, nil
}

func (p clusterMeteringRepoImp) LoadLatest(
	ctx context.Context,
	clusterID uint64,
) (*entity.ClusterMetering, bizerr.BizError) {
	wdb := p.dbProvider.ReadDB(ctx)
	metering := &schema.ClusterMetering{}
	if err := wdb.Where("cluster_id=?", clusterID).
		Order("start_ts desc").First(metering).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find first cluster metering")
	}

	en, err := ClusterMeteringModelToEn(metering)
	if err != nil {
		return nil, err
	}
	return en, nil
}

func (p clusterMeteringRepoImp) Save(ctx context.Context, ens ...*entity.ClusterMetering) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)
	for _, en := range ens {
		record := ClusterMeteringEnToModel(en)
		if err := wdb.Save(record).Error; err != nil {
			return bizerr.WrapGormError(err, "save cluster metering, en=%+v", en)
		}
		// write back the generated ID to entity
		en.ID = uint64(record.ID)
	}

	return nil
}

func (p clusterMeteringRepoImp) Clean(ctx context.Context, before time.Time, limit int) (int64, bizerr.BizError) {
	wdb := p.dbProvider.WriteDB(ctx).Unscoped()
	result := wdb.Where("created_at < ? AND latest = 0", before).Limit(limit).Delete(&schema.ClusterMetering{})
	if result.Error != nil {
		return 0, bizerr.WrapGormError(result.Error, "clean cluster metering")
	}
	return result.RowsAffected, nil
}
