package backup

import (
	"context"
	"time"

	"github.com/AlekSi/pointer"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	bizerr_wrapper "github.com/tidbcloud/dataflow-service/pkg/bizerr-wrapper"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"
)

type logBackupSizeRepoImpl struct {
	dbProvider db.Provider
}

func NewLogBackupSizeRepo(provider db.Provider) LogBackupSizeRepo {
	return &logBackupSizeRepoImpl{
		dbProvider: provider,
	}
}

func (b *logBackupSizeRepoImpl) Load(ctx context.Context, id entity.ID) (*entity.LogBackupSize, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	logBackupSize := new(entity.LogBackupSize)
	if err := entity.NewLogBackupSizeQuerySet(rdb).IDEq(id).One(logBackupSize); err != nil {
		return nil, bizerr.WrapGormError(err, "load logBackupSize by id %d", id)
	}
	return logBackupSize, nil
}

func (b *logBackupSizeRepoImpl) List(ctx context.Context, ops ...LogBackupSizeFilterOp,
) ([]entity.LogBackupSize, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupSizeQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var logBackupSizes []entity.LogBackupSize
	if err := qs.All(&logBackupSizes); err != nil {
		return nil, bizerr.WrapGormError(err, "list logBackupSizes occur error")
	}
	return logBackupSizes, nil
}

func (b *logBackupSizeRepoImpl) Get(ctx context.Context, ops ...LogBackupSizeFilterOp,
) (*entity.LogBackupSize, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupSizeQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	logBackupSize := &entity.LogBackupSize{}
	if err := qs.One(logBackupSize); err != nil {
		return nil, bizerr.WrapGormError(err, "get logBackupSize occur error")
	}
	return logBackupSize, nil
}

func (b *logBackupSizeRepoImpl) Save(ctx context.Context, en *entity.LogBackupSize) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, en); err != nil {
		return err.Chain("save LogBackupSize, id=%d", en.ID)
	}
	return nil
}

type BackupSettingRepoImpl struct {
	dbProvider db.Provider
}

var _ BackupSettingRepo = (*BackupSettingRepoImpl)(nil)

func NewBackupSettingRepo(provider db.Provider) BackupSettingRepo {
	return &BackupSettingRepoImpl{
		dbProvider: provider,
	}
}

func (b *BackupSettingRepoImpl) Load(ctx context.Context, id entity.ID) (*entity.BackupSetting, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	backupSetting := new(entity.BackupSetting)
	if err := entity.NewBackupSettingQuerySet(rdb).IDEq(id).One(backupSetting); err != nil {
		return nil, bizerr.WrapGormError(err, "load backup setting by id %d", id)
	}
	return backupSetting, nil
}

func (b *BackupSettingRepoImpl) LoadByClusterID(ctx context.Context, clusterID uint64) (*entity.BackupSetting, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	backupSetting := new(entity.BackupSetting)
	if err := entity.NewBackupSettingQuerySet(rdb).ClusterIDEq(entity.ID(clusterID)).One(backupSetting); err != nil {
		return nil, bizerr.WrapGormError(err, "load backup setting by cluster id %d", clusterID)
	}
	return backupSetting, nil
}

func (b *BackupSettingRepoImpl) GetByFilter(ctx context.Context, filter *BackupSettingFilter) (
	*entity.BackupSetting, bizerr.BizError,
) {
	rdb := b.dbProvider.ReadDB(ctx)
	backupSetting := new(entity.BackupSetting)
	rdb = b.buildTxBackupSettingFilter(rdb, filter)
	if result := rdb.First(backupSetting); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "get backup setting occur error")
	}
	return backupSetting, nil
}

func (b *BackupSettingRepoImpl) ListByFilter(ctx context.Context, filter *BackupSettingFilter) (
	[]*entity.BackupSetting, bizerr.BizError,
) {
	rdb := b.dbProvider.ReadDB(ctx)
	var backupSettings []*entity.BackupSetting
	rdb = b.buildTxBackupSettingFilter(rdb, filter)
	if result := rdb.Find(&backupSettings); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "list backup setting occur error")
	}
	return backupSettings, nil
}

func (b *BackupSettingRepoImpl) List(ctx context.Context, ops ...BackupSettingFilterOp,
) ([]entity.BackupSetting, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupSettingQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var backupSettings []entity.BackupSetting
	if err := qs.All(&backupSettings); err != nil {
		return nil, bizerr.WrapGormError(err, "list backupSettings occur error")
	}
	return backupSettings, nil
}

func (b *BackupSettingRepoImpl) Get(ctx context.Context, ops ...BackupSettingFilterOp,
) (*entity.BackupSetting, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupSettingQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	backupSetting := &entity.BackupSetting{}
	if err := qs.One(backupSetting); err != nil {
		return nil, bizerr.WrapGormError(err, "get backupSetting occur error")
	}
	return backupSetting, nil
}

func (b *BackupSettingRepoImpl) Save(ctx context.Context, setting *entity.BackupSetting) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, setting); err != nil {
		return err.Chain("save backup setting, id=%d", setting.ID)
	}
	return nil
}

func (b *BackupSettingRepoImpl) buildTxBackupSettingFilter(tx *gorm.DB, filter *BackupSettingFilter) *gorm.DB {
	if filter.ClusterID != nil {
		tx = tx.Where("cluster_id = ?", filter.ClusterID)
	}
	if filter.ProjectID != nil {
		tx = tx.Where("project_id = ?", filter.ProjectID)
	}
	if filter.TenantID != nil {
		tx = tx.Where("tenant_id = ?", filter.TenantID)
	}
	return tx
}

func (b *BackupSettingRepoImpl) Count(ctx context.Context, ops ...BackupSettingFilterOp,
) (int64, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupSettingQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	count, err := qs.Offset(-1).Limit(-1).Count()
	if err != nil {
		return 0, bizerr.WrapGormError(err, "count backup setting occur error")
	}
	return count, nil
}

type BackupScheduleRepoImpl struct {
	dbProvider db.Provider
}

var _ BackupScheduleRepo = (*BackupScheduleRepoImpl)(nil)

func NewBackupScheduleRepo(dbProvider db.Provider) BackupScheduleRepo {
	return &BackupScheduleRepoImpl{dbProvider: dbProvider}
}

func (c *BackupScheduleRepoImpl) List(
	ctx context.Context, ops ...BackupScheduleFilterOp,
) ([]entity.BackupSchedule, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupScheduleQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var backupSchedules []entity.BackupSchedule
	if err := qs.All(&backupSchedules); err != nil {
		return nil, bizerr.WrapGormError(err, "list backupSchedules occur error")
	}
	return backupSchedules, nil
}

func (c *BackupScheduleRepoImpl) Count(
	ctx context.Context, ops ...BackupScheduleFilterOp,
) (int64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	qs := entity.NewBackupScheduleQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	count, err := qs.Offset(-1).Limit(-1).Count()
	if err != nil {
		return 0, bizerr.WrapGormError(err, "count backups schedule occur error")
	}
	return count, nil
}

func (c *BackupScheduleRepoImpl) Create(ctx context.Context, bs *entity.BackupSchedule) bizerr.BizError {
	wdb := c.dbProvider.WriteDB(ctx)
	if result := wdb.Create(bs); result.Error != nil {
		return bizerr.WrapGormError(result.Error, "add backupSchedule")
	}
	return nil
}

func (c *BackupScheduleRepoImpl) Load(ctx context.Context, id uint64) (*entity.BackupSchedule, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	var backupSchedule entity.BackupSchedule
	if err := rdb.Model(&entity.BackupSchedule{}).Where("id = ?", id).First(&backupSchedule).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load backupSchedule by id %d", id)
	}
	return &backupSchedule, nil
}

func (c *BackupScheduleRepoImpl) CASUpdate(ctx context.Context, backupSchedule *entity.BackupSchedule) bizerr.BizError {
	wdb := c.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, backupSchedule); err != nil {
		return err.Chain("save backupSchedule, id=%d", backupSchedule.ID)
	}
	return nil
}

func (c *BackupScheduleRepoImpl) Patch(ctx context.Context,
	id entity.ID,
	patches map[string]interface{},
) bizerr.BizError {
	if id == 0 {
		return bizerr_wrapper.NewInvalidInputError("id is required")
	}
	if patches == nil || len(patches) == 0 {
		return bizerr_wrapper.NewInvalidInputError("no content in patches")
	}

	wdb := c.dbProvider.WriteDB(ctx)
	bs := entity.BackupSchedule{}
	bs.ID = id
	// Notice: gorm also support update attributes with `struct`,
	// refer to https://gorm.io/docs/update.html#Updates-multiple-columns
	// but we won't do it in this way if we want to update a field to zero value,
	// we can not achieve it since zero value means no update.
	err := wdb.Model(&bs).Updates(patches).Error
	return bizerr.WrapGormError(err,
		"failed to patch backupSchedule by id: %d, patch content: %+v", id, patches)
}

func (c *BackupScheduleRepoImpl) PatchAuto(ctx context.Context,
	clusterId entity.ID,
	patches map[string]interface{},
) bizerr.BizError {
	if clusterId == 0 {
		return bizerr_wrapper.NewInvalidInputError("cluster id is required")
	}
	if patches == nil || len(patches) == 0 {
		return bizerr_wrapper.NewInvalidInputError("no content in patches")
	}

	wdb := c.dbProvider.WriteDB(ctx)
	// query to get primary key, i.e. ID
	bs := entity.BackupSchedule{}
	err := wdb.Where("cluster_id = ? and type = 'auto'", clusterId).First(&bs).Error
	if err != nil {
		return bizerr.WrapGormError(err,
			"failed to query backupSchedule by cluster_id: %d in Patch", clusterId)
	}
	err = c.Patch(ctx, bs.ID, patches)
	return bizerr.WrapGormError(err,
		"failed to patch backupSchedule by cluster_id: %d, patch content: %+v", clusterId, patches)
}

func (c *BackupScheduleRepoImpl) SoftDelete(ctx context.Context, id entity.ID) bizerr.BizError {
	if id == 0 {
		return bizerr_wrapper.NewInvalidInputError("id is required")
	}
	wdb := c.dbProvider.WriteDB(ctx)
	err := wdb.Model(&entity.BackupSchedule{}).
		Where("id = ? and deleted_at is null", id).
		Update("deleted_at", pointer.To(time.Now())).
		Error

	return bizerr.WrapGormError(err, "failed to delete backupSchedule by id %d", id)
}

func (c *BackupScheduleRepoImpl) SoftDeleteAuto(ctx context.Context, clusterId entity.ID) bizerr.BizError {
	if clusterId == 0 {
		return bizerr_wrapper.NewInvalidInputError("cluster id is required")
	}
	wdb := c.dbProvider.WriteDB(ctx)
	err := wdb.Model(&entity.BackupSchedule{}).
		Where("cluster_id = ? and deleted_at is null and type = 'auto'", clusterId).
		Update("deleted_at", pointer.To(time.Now())).
		Error

	return bizerr.WrapGormError(err, "failed to delete backupSchedule by cluster_id %d", clusterId)
}

func (c *BackupScheduleRepoImpl) GetByFilter(ctx context.Context, filter *BackupScheduleFilter) (
	*entity.BackupSchedule, bizerr.BizError,
) {
	rdb := c.dbProvider.ReadDB(ctx)
	tx := buildTxBackupScheduleFilter(rdb, filter)
	backupSchedule := new(entity.BackupSchedule)
	if result := tx.First(backupSchedule); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "get backupSchedule")
	}
	return backupSchedule, nil
}

func (c *BackupScheduleRepoImpl) ListByFilter(ctx context.Context, filter *BackupScheduleFilter) (
	[]*entity.BackupSchedule, bizerr.BizError,
) {
	rdb := c.dbProvider.ReadDB(ctx)
	tx := buildTxBackupScheduleFilter(rdb, filter)
	var backupSchedules []*entity.BackupSchedule
	if err := tx.Model(&entity.BackupSchedule{}).Find(&backupSchedules).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "list backupSchedule by filter, filter is %v", filter)
	}
	return backupSchedules, nil
}

func buildTxBackupScheduleFilter(tx *gorm.DB, filter *BackupScheduleFilter) *gorm.DB {
	if filter.ClusterID != nil {
		tx = tx.Where("cluster_id = ?", filter.ClusterID)
	}
	if filter.Type != nil {
		tx = tx.Where("type = ?", *filter.Type)
	}
	return tx
}

type BackupRepoImpl struct {
	dbProvider db.Provider
}

var _ BackupRepo = (*BackupRepoImpl)(nil)

func NewBackupRepo(dbProvider db.Provider) BackupRepo {
	return &BackupRepoImpl{dbProvider: dbProvider}
}

func (c *BackupRepoImpl) List(
	ctx context.Context, ops ...BackupFilterOp,
) ([]entity.Backup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var backups []entity.Backup
	if err := qs.All(&backups); err != nil {
		return nil, bizerr.WrapGormError(err, "list backups occur error")
	}
	return backups, nil
}

func (c *BackupRepoImpl) ListExternal(
	ctx context.Context, ops ...BackupFilterOp,
) ([]entity.Backup, bizerr.BizError) {
	ops = append(ops, func(qs entity.BackupQuerySet) entity.BackupQuerySet {
		return qs.IsInternalEq(false)
	})
	return c.List(ctx, ops...)
}

func (c *BackupRepoImpl) Get(
	ctx context.Context, ops ...BackupFilterOp,
) (*entity.Backup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var backup entity.Backup
	if err := qs.One(&backup); err != nil {
		return nil, bizerr.WrapGormError(err, "get backups occur error")
	}
	return &backup, nil
}

func (c *BackupRepoImpl) Count(ctx context.Context, ops ...BackupFilterOp,
) (int64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewBackupQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	count, err := qs.Offset(-1).Limit(-1).Count()
	if err != nil {
		return 0, bizerr.WrapGormError(err, "count backups occur error")
	}
	return count, nil
}

func (c *BackupRepoImpl) CountExternal(ctx context.Context, ops ...BackupFilterOp,
) (int64, bizerr.BizError) {
	ops = append(ops, func(qs entity.BackupQuerySet) entity.BackupQuerySet {
		return qs.IsInternalEq(false)
	})
	return c.Count(ctx, ops...)
}

func (c *BackupRepoImpl) Load(ctx context.Context, id uint64) (*entity.Backup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	var backup entity.Backup
	if err := rdb.Model(&entity.Backup{}).Preload("BackupSchedule").Where("id = ?", id).First(&backup).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load backup by id %d", id)
	}
	return &backup, nil
}

func (c *BackupRepoImpl) Save(ctx context.Context, backup *entity.Backup) bizerr.BizError {
	wdb := c.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, backup); err != nil {
		return err.Chain("save backup, id=%d", backup.ID)
	}
	return nil
}

func (c *BackupRepoImpl) CountByFilter(ctx context.Context, filter BackupFilter) (int64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	tx := buildTxBackupFilter(rdb, filter)
	var count int64
	if err := tx.Model(&entity.Backup{}).Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "count backup by filter is %v", filter)
	}
	return count, nil
}

func (c *BackupRepoImpl) ListByFilter(ctx context.Context, filter BackupFilter) (
	[]*entity.Backup, bizerr.BizError,
) {
	rdb := c.dbProvider.ReadDB(ctx)
	var backups []*entity.Backup
	tx := buildTxBackupFilter(rdb, filter).Preload("BackupSchedule")
	if err := tx.Model(&entity.Backup{}).Find(&backups).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "list backup by filter is %v", filter)
	}
	return backups, nil
}

func (c *BackupRepoImpl) ListClustersWithBackup(ctx context.Context, projectID uint64) ([]uint64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	rows, err := rdb.Model(&entity.Backup{}).
		Select("cluster_id").
		Where("is_internal = 0 and deleted_at is null and project_id = ?", projectID).
		Group("cluster_id").Rows()
	if err != nil {
		return nil, bizerr.WrapGormError(err, "list clusters with backup error")
	}
	defer rows.Close()

	var clusterIDs []uint64
	for rows.Next() {
		var clusterID uint64
		if err := rows.Scan(&clusterID); err != nil {
			return nil, bizerr.WrapGormError(err, "scan cluster id error")
		}
		clusterIDs = append(clusterIDs, clusterID)
	}
	return clusterIDs, nil
}

func (c *BackupRepoImpl) ListClustersWithBackupByProjects(
	ctx context.Context, projectIDs []uint64,
) ([]uint64, bizerr.BizError) {
	if len(projectIDs) == 0 {
		return []uint64{}, nil
	}
	rdb := c.dbProvider.ReadDB(ctx)
	rows, err := rdb.Model(&entity.Backup{}).
		Select("cluster_id").
		Where("is_internal = 0 and deleted_at is null and project_id in (?)", projectIDs).
		Group("cluster_id").Rows()
	if err != nil {
		return nil, bizerr.WrapGormError(err, "list clusters with backup by projects error")
	}
	defer rows.Close()

	var clusterIDs []uint64
	for rows.Next() {
		var clusterID uint64
		if err := rows.Scan(&clusterID); err != nil {
			return nil, bizerr.WrapGormError(err, "scan cluster id error")
		}
		clusterIDs = append(clusterIDs, clusterID)
	}
	return clusterIDs, nil
}

func (c *BackupRepoImpl) ListClustersWithBackupByOrg(ctx context.Context, orgID uint64) ([]uint64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	rows, err := rdb.Model(&entity.Backup{}).
		Select("cluster_id").
		Where("is_internal = 0 and deleted_at is null and tenant_id = ?", orgID).
		Group("cluster_id").Rows()
	if err != nil {
		return nil, bizerr.WrapGormError(err, "list clusters with backup by org error")
	}
	defer rows.Close()

	var clusterIDs []uint64
	for rows.Next() {
		var clusterID uint64
		if err := rows.Scan(&clusterID); err != nil {
			return nil, bizerr.WrapGormError(err, "scan cluster id error")
		}
		clusterIDs = append(clusterIDs, clusterID)
	}
	return clusterIDs, nil
}

func buildTxBackupFilter(tx *gorm.DB, filter BackupFilter) *gorm.DB {
	if filter.ClusterID != nil {
		tx = tx.Where("cluster_id = ?", filter.ClusterID)
	}
	if filter.ProjectID != nil {
		tx = tx.Where("project_id = ?", filter.ProjectID)
	}
	if filter.BucketID != nil {
		tx = tx.Where("bucket_id = ?", filter.BucketID)
	}
	if len(filter.BackupScheduleIDs) > 0 {
		tx = tx.Where("backup_schedule_id in (?)", filter.BackupScheduleIDs)
	}

	if filter.CleanedUp != nil {
		if *filter.CleanedUp {
			tx = tx.Where("cleaned_up = 1")
		} else {
			tx = tx.Where("cleaned_up = 0")
		}
	}

	if filter.Deleted != nil {
		if *filter.Deleted {
			tx = tx.Where("deleted_at is not null")
		} else {
			tx = tx.Where("deleted_at is null")
		}
	}

	if filter.LeakChecked != nil {
		if *filter.LeakChecked {
			tx = tx.Where("leak_checked = 1")
		} else {
			tx = tx.Where("leak_checked = 0")
		}
	}

	if len(filter.Statuses) != 0 {
		tx = tx.Where("status in (?)", filter.Statuses)
	}

	return tx
}

type LogBackupRepoImpl struct {
	dbProvider db.Provider
}

var _ LogBackupRepo = (*LogBackupRepoImpl)(nil)

func NewLogBackupRepo(dbProvider db.Provider) LogBackupRepo {
	return &LogBackupRepoImpl{dbProvider: dbProvider}
}

func (c *LogBackupRepoImpl) Get(ctx context.Context, ops ...LogBackupFilterOp,
) (*entity.LogBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	logBackup := &entity.LogBackup{}
	if err := qs.One(logBackup); err != nil {
		return nil, bizerr.WrapGormError(err, "get logBackup occur error")
	}
	return logBackup, nil
}

func (c *LogBackupRepoImpl) List(
	ctx context.Context, ops ...LogBackupFilterOp,
) ([]entity.LogBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var logBackups []entity.LogBackup
	if err := qs.All(&logBackups); err != nil {
		return nil, bizerr.WrapGormError(err, "list logBackups occur error")
	}
	return logBackups, nil
}

func (c *LogBackupRepoImpl) Count(ctx context.Context, ops ...LogBackupFilterOp,
) (int64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	count, err := qs.Offset(-1).Limit(-1).Count()
	if err != nil {
		return 0, bizerr.WrapGormError(err, "count logBackups occur error")
	}
	return count, nil
}

func (c *LogBackupRepoImpl) Load(ctx context.Context, id uint64) (*entity.LogBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	var backup entity.LogBackup
	if err := rdb.Model(&entity.LogBackup{}).Where("id = ?", id).First(&backup).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load log backup by id %d", id)
	}
	return &backup, nil
}

func (c *LogBackupRepoImpl) Save(ctx context.Context, backup *entity.LogBackup) bizerr.BizError {
	wdb := c.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, backup); err != nil {
		return err.Chain("save log backup, id=%d", backup.ID)
	}
	return nil
}

func (c *LogBackupRepoImpl) CountByFilter(ctx context.Context, filter LogBackupFilter) (int64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	tx := buildTxLogBackupFilter(rdb, filter)
	var count int64
	if err := tx.Model(&entity.LogBackup{}).Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "count log backup by filter is %v", filter)
	}
	return count, nil
}

func (c *LogBackupRepoImpl) ListByFilter(ctx context.Context, filter LogBackupFilter) (
	[]*entity.LogBackup, bizerr.BizError,
) {
	rdb := c.dbProvider.ReadDB(ctx)
	var logBackups []*entity.LogBackup
	tx := buildTxLogBackupFilter(rdb, filter)
	if err := tx.Model(&entity.LogBackup{}).Find(&logBackups).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "list log backup by filter is %v", filter)
	}
	return logBackups, nil
}

func buildTxLogBackupFilter(tx *gorm.DB, filter LogBackupFilter) *gorm.DB {
	if filter.ClusterID != nil {
		tx = tx.Where("cluster_id = ?", filter.ClusterID)
	}
	if filter.ProjectID != nil {
		tx = tx.Where("project_id = ?", filter.ProjectID)
	}
	if filter.BucketID != nil {
		tx = tx.Where("bucket_id = ?", filter.BucketID)
	}
	if len(filter.BackupScheduleIDs) > 0 {
		tx = tx.Where("backup_schedule_id in (?)", filter.BackupScheduleIDs)
	}

	if filter.CleanedUp != nil {
		if *filter.CleanedUp {
			tx = tx.Where("cleaned_up = 1")
		} else {
			tx = tx.Where("cleaned_up = 0")
		}
	}

	if filter.Status != nil {
		tx = tx.Where("status = ?", filter.Status)
	}
	return tx
}

type LogBackupTruncateRepoImpl struct {
	dbProvider db.Provider
}

func NewLogBackupTruncateRepo(dbProvider db.Provider) LogBackupTruncateRepo {
	return &LogBackupTruncateRepoImpl{dbProvider: dbProvider}
}

func (b *LogBackupTruncateRepoImpl) Save(ctx context.Context, en *entity.LogBackupTruncate) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, en); err != nil {
		return err.Chain("save logBackupTruncate, id=%d", en.ID)
	}
	return nil
}

func (b *LogBackupTruncateRepoImpl) List(ctx context.Context, ops ...LogBackupTruncateFilterOp,
) ([]entity.LogBackupTruncate, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupTruncateQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var logBackupTruncates []entity.LogBackupTruncate
	if err := qs.All(&logBackupTruncates); err != nil {
		return nil, bizerr.WrapGormError(err, "list logBackupTruncates occur error")
	}
	return logBackupTruncates, nil
}

func (b *LogBackupTruncateRepoImpl) Count(ctx context.Context, ops ...LogBackupTruncateFilterOp,
) (int64, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewLogBackupTruncateQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	count, err := qs.Offset(-1).Limit(-1).Count()
	if err != nil {
		return 0, bizerr.WrapGormError(err, "count logBackupTruncates occur error")
	}
	return count, nil
}

func (b *LogBackupTruncateRepoImpl) Load(ctx context.Context, id uint64) (*entity.LogBackupTruncate, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	logBackupTruncate := new(entity.LogBackupTruncate)
	if err := entity.NewLogBackupTruncateQuerySet(rdb).IDEq(entity.ID(id)).One(logBackupTruncate); err != nil {
		return nil, bizerr.WrapGormError(err, "load logBackupTruncate occur error")
	}
	return logBackupTruncate, nil
}

type PitBackupSwitchRepoImpl struct {
	dbProvider db.Provider
}

func NewPitBackupSwitchRepo(dbProvider db.Provider) PitBackupSwitchRepo {
	return &PitBackupSwitchRepoImpl{dbProvider: dbProvider}
}

func (b *PitBackupSwitchRepoImpl) Save(ctx context.Context, en *entity.PitBackupSwitch) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, en); err != nil {
		return err.Chain("save PitBackupSwitch, id=%d", en.ID)
	}
	return nil
}

func (b *PitBackupSwitchRepoImpl) Load(ctx context.Context, id uint64) (*entity.PitBackupSwitch, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	ent := new(entity.PitBackupSwitch)
	if err := entity.NewPitBackupSwitchQuerySet(rdb).IDEq(entity.ID(id)).One(ent); err != nil {
		return nil, bizerr.WrapGormError(err, "load PitBackupSwitch occur error")
	}
	return ent, nil
}

func (b *PitBackupSwitchRepoImpl) LoadByClusterID(ctx context.Context, clusterID uint64) (*entity.PitBackupSwitch, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	ent := new(entity.PitBackupSwitch)
	if err := entity.NewPitBackupSwitchQuerySet(rdb).ClusterIDEq(entity.ID(clusterID)).One(ent); err != nil {
		return nil, bizerr.WrapGormError(err, "failed to load PitBackupSwitch by clusterID")
	}
	return ent, nil
}

func (b *PitBackupSwitchRepoImpl) List(ctx context.Context, ops ...PitBackupSwitchFilterOp,
) ([]entity.PitBackupSwitch, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	qs := entity.NewPitBackupSwitchQuerySet(rdb)
	for _, op := range ops {
		qs = op(qs)
	}
	var entList []entity.PitBackupSwitch
	if err := qs.All(&entList); err != nil {
		return nil, bizerr.WrapGormError(err, "list PitBackupSwitch occur error")
	}
	return entList, nil
}

type BackupExportRepoImpl struct {
	dbProvider db.Provider
}

func NewBackupExportRepo(provider db.Provider) BackupExportRepo {
	return &BackupExportRepoImpl{
		dbProvider: provider,
	}
}

func (b *BackupExportRepoImpl) Load(ctx context.Context, id uint64) (*entity.BackupExport, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	backupExport := new(entity.BackupExport)
	if err := rdb.
		Preload("Backup").
		Preload("Backup.BackupSchedule").
		Where("id = ?", id).First(backupExport).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load backup export by id %d", id)
	}
	return backupExport, nil
}

func (b *BackupExportRepoImpl) Save(ctx context.Context, en *entity.BackupExport) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, en); err != nil {
		return err.Chain("save BackupExport, id=%d", en.ID)
	}
	return nil
}

func (b *BackupExportRepoImpl) buildTxBackupExportFilter(
	tx *gorm.DB,
	filter *BackupExportFilter,
) *gorm.DB {
	if filter == nil {
		return tx
	}
	if filter.TenantID != nil {
		tx = tx.Where("tenant_id = ?", *filter.TenantID)
	}
	if filter.ProjectID != nil {
		tx = tx.Where("project_id = ?", *filter.ProjectID)
	}
	if filter.ClusterID != nil {
		tx = tx.Where("cluster_id = ?", *filter.ClusterID)
	}
	if filter.BackupID != nil {
		tx = tx.Where("backup_id = ?", *filter.BackupID)
	}
	if filter.Provider != nil {
		tx = tx.Where("provider = ?", *filter.Provider)
	}
	if len(filter.Statuses) != 0 {
		tx = tx.Where("status in (?)", filter.Statuses)
	}
	return tx
}

func (b *BackupExportRepoImpl) ListByFilter(
	ctx context.Context,
	filter *BackupExportFilter,
	ops ...ListBackupExportOp,
) ([]entity.BackupExport, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	var backupExports []entity.BackupExport
	for _, op := range ops {
		rdb = op(rdb)
	}
	rdb = b.buildTxBackupExportFilter(rdb, filter)
	if result := rdb.Preload("Backup").Preload("Backup.BackupSchedule").Find(&backupExports); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "list backup exports occur error")
	}
	return backupExports, nil
}

func (b *BackupExportRepoImpl) CountByFilter(
	ctx context.Context,
	filter *BackupExportFilter,
) (int64, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	var count int64
	rdb = b.buildTxBackupExportFilter(rdb, filter)
	if result := rdb.Model(&entity.BackupExport{}).Count(&count); result.Error != nil {
		return 0, bizerr.WrapGormError(result.Error, "count backup exports occur error")
	}
	return count, nil
}

type CompactBackupRepoImpl struct {
	dbProvider db.Provider
}

var _ CompactBackupRepo = (*CompactBackupRepoImpl)(nil)

func NewCompactBackupRepo(provider db.Provider) CompactBackupRepo {
	return &CompactBackupRepoImpl{
		dbProvider: provider,
	}
}

func (c *CompactBackupRepoImpl) Load(ctx context.Context, id uint64) (*entity.CompactBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	compactBackup := new(entity.CompactBackup)
	if result := rdb.Where("id = ?", id).First(compactBackup); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "load compact backup by id %d", id)
	}
	return compactBackup, nil
}

func (c *CompactBackupRepoImpl) Save(ctx context.Context, cb *entity.CompactBackup) bizerr.BizError {
	wdb := c.dbProvider.WriteDB(ctx)
	if err := entity.SaveWithVersioning(wdb, cb); err != nil {
		return err.Chain("save compact backup, id=%d", cb.ID)
	}
	return nil
}

func (c *CompactBackupRepoImpl) ListByFilter(
	ctx context.Context, filter *CompactBackupFilter,
) ([]*entity.CompactBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	var compactBackups []*entity.CompactBackup
	rdb = c.buildTxCompactBackupFilter(rdb, filter)
	if result := rdb.Find(&compactBackups); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "list compact backups occur error")
	}
	return compactBackups, nil
}

func (c *CompactBackupRepoImpl) CountByFilter(
	ctx context.Context, filter *CompactBackupFilter,
) (int64, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	var count int64
	rdb = c.buildTxCompactBackupFilter(rdb, filter)
	if result := rdb.Model(&entity.CompactBackup{}).Count(&count); result.Error != nil {
		return 0, bizerr.WrapGormError(result.Error, "count compact backups occur error")
	}
	return count, nil
}

func (c *CompactBackupRepoImpl) GetRunningByCluster(
	ctx context.Context, clusterID uint64,
) (*entity.CompactBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	compactBackup := new(entity.CompactBackup)
	if result := rdb.Where("cluster_id = ? AND status IN (?)", clusterID, []entity.CompactBackupStatus{
		entity.CompactBackupStatusPending,
		entity.CompactBackupStatusRunning,
	}).Where("deleted_at IS NULL").First(compactBackup); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "get running compact backup by cluster %d", clusterID)
	}
	return compactBackup, nil
}

func (c *CompactBackupRepoImpl) ListByLogBackup(
	ctx context.Context, logBackupID uint64,
) ([]*entity.CompactBackup, bizerr.BizError) {
	rdb := c.dbProvider.ReadDB(ctx)
	var compactBackups []*entity.CompactBackup
	if result := rdb.Where("log_backup_id = ?", logBackupID).
		Where("deleted_at IS NULL").
		Find(&compactBackups); result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "list compact backups by log backup %d", logBackupID)
	}
	return compactBackups, nil
}

func (c *CompactBackupRepoImpl) buildTxCompactBackupFilter(tx *gorm.DB, filter *CompactBackupFilter) *gorm.DB {
	if filter == nil {
		return tx.Where("deleted_at IS NULL")
	}

	tx = tx.Where("deleted_at IS NULL")

	if filter.ClusterID != nil {
		tx = tx.Where("cluster_id = ?", *filter.ClusterID)
	}

	if filter.LogBackupID != nil {
		tx = tx.Where("log_backup_id = ?", *filter.LogBackupID)
	}

	if filter.Status != nil {
		tx = tx.Where("status = ?", *filter.Status)
	}

	if len(filter.Statuses) > 0 {
		tx = tx.Where("status IN (?)", filter.Statuses)
	}

	if len(filter.IDs) > 0 {
		tx = tx.Where("id IN (?)", filter.IDs)
	}

	return tx
}
