package backup

import (
	"context"

	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/pkgv2/bizerr"
	"gorm.io/gorm"
)

type (
	BackupScheduleFilterOp    func(qs entity.BackupScheduleQuerySet) entity.BackupScheduleQuerySet
	BackupFilterOp            func(qs entity.BackupQuerySet) entity.BackupQuerySet
	LogBackupFilterOp         func(qs entity.LogBackupQuerySet) entity.LogBackupQuerySet
	LogBackupTruncateFilterOp func(qs entity.LogBackupTruncateQuerySet) entity.LogBackupTruncateQuerySet
	BackupSettingFilterOp     func(qs entity.BackupSettingQuerySet) entity.BackupSettingQuerySet
	PitBackupSwitchFilterOp   func(qs entity.PitBackupSwitchQuerySet) entity.PitBackupSwitchQuerySet
	LogBackupSizeFilterOp     func(qs entity.LogBackupSizeQuerySet) entity.LogBackupSizeQuerySet
	ListBackupExportOp        func(tx *gorm.DB) *gorm.DB
)

type BackupScheduleFilter struct {
	ClusterID *uint64
	Type      *entity.BackupType
}

/*
BackupScheduleRepo provide CURD support for BackupSchedule entity.
The fields of BackupSchedule entity are updated in different patterns, which will also need different implementation.
The update patterns:
1. DeletedAt
a) triggered by: cluster deletion event
b) update logic: independent update, i.e. not depends on others' fileds
2. BackupCycle
a) triggered by: user modification (high priority)
b) update logic: will cause NextBackupTime recalculation
3. Schedule
a) triggered by: user modification (high priority)
b) update logic: will cause NextBackupTime recalculation
4. NextBackupTime
a1) triggered by: background scheduling logic (low priority)
b1) update logic: affected by background scheduling over time
a2) triggered by: user updates other fields (high priority)
b2) update logic: affected by BackupCycle and Schedule changes
5. RetentionPeriod
a) triggered by: user modification
b) update logic: independent update
6. LastCleanAt
a) triggered by: background cleaning logic
b) update logic: independent update
7. CleanedUp
a) triggered by: background cleaning logic
b) update logic: independent update

The implementation patterns:
1. For independently updated fields, there is no need to use CAS update, just update directly
2. For fields that are not updated independently
a) Logic for low-priority updates requires the use of CAS updates and requires retrying after conflicts.
b) Logic for high-priority updates, no need to use CAS updates,  just update directly
 3. Background logic, such as scheduling and cleaning, which implemented as a retry logic that can be retried after
    failure, just like Reconcile.
*/
//nolint:interfacebloat
type BackupScheduleRepo interface {
	Create(ctx context.Context, bs *entity.BackupSchedule) bizerr.BizError
	// CASUpdate CAS(Compare-and-Swap) update a BackupSchedule, sort of optimistic lock.
	// If Versioning changed, return failure
	CASUpdate(ctx context.Context, bs *entity.BackupSchedule) bizerr.BizError
	// Patch bypass optimistic lock, update specific fields of BackupSchedule instead of replace all,
	// Versioning and UpdatedAt are updated automatically
	Patch(ctx context.Context, id entity.ID, patches map[string]interface{}) bizerr.BizError
	// PatchAuto bypass optimistic lock, update specific fields of auto BackupSchedule instead of replace all,
	// Versioning and UpdatedAt are updated automatically
	PatchAuto(ctx context.Context, clusterId entity.ID, patches map[string]interface{}) bizerr.BizError
	// SoftDelete bypass optimistic lock, mark BackupSchedule as deleted,
	// Versioning and UpdatedAt are updated automatically
	SoftDelete(ctx context.Context, id entity.ID) bizerr.BizError
	// SoftDeleteAuto bypass optimistic lock, mark auto BackupSchedule as deleted,
	// Versioning and UpdatedAt are updated automatically
	SoftDeleteAuto(ctx context.Context, clusterId entity.ID) bizerr.BizError

	Load(ctx context.Context, id uint64) (*entity.BackupSchedule, bizerr.BizError)
	GetByFilter(ctx context.Context, filter *BackupScheduleFilter) (*entity.BackupSchedule, bizerr.BizError)
	ListByFilter(ctx context.Context, filter *BackupScheduleFilter) ([]*entity.BackupSchedule, bizerr.BizError)
	Count(ctx context.Context, ops ...BackupScheduleFilterOp) (int64, bizerr.BizError)
	List(ctx context.Context, ops ...BackupScheduleFilterOp) ([]entity.BackupSchedule, bizerr.BizError)
}

type BackupFilter struct {
	ClusterID *uint64
	ProjectID *uint64
	BucketID  *uint64

	CleanedUp   *bool
	Deleted     *bool
	LeakChecked *bool

	Statuses []entity.BackupStatus

	BackupScheduleIDs []uint64
}

//nolint:interfacebloat
type BackupRepo interface {
	Load(ctx context.Context, id uint64) (*entity.Backup, bizerr.BizError)
	Save(ctx context.Context, b *entity.Backup) bizerr.BizError

	CountByFilter(ctx context.Context, filter BackupFilter) (int64, bizerr.BizError)
	ListByFilter(ctx context.Context, filter BackupFilter) ([]*entity.Backup, bizerr.BizError)

	Get(ctx context.Context, ops ...BackupFilterOp) (*entity.Backup, bizerr.BizError)
	List(ctx context.Context, ops ...BackupFilterOp) ([]entity.Backup, bizerr.BizError)
	Count(ctx context.Context, ops ...BackupFilterOp) (int64, bizerr.BizError)
	// ListExternal list backups with is_internal == false
	ListExternal(ctx context.Context, ops ...BackupFilterOp) ([]entity.Backup, bizerr.BizError)
	// CountExternal count backups with is_internal == false
	CountExternal(ctx context.Context, ops ...BackupFilterOp) (int64, bizerr.BizError)
	ListClustersWithBackup(ctx context.Context, projectID uint64) ([]uint64, bizerr.BizError)
	ListClustersWithBackupByProjects(ctx context.Context, projectIDs []uint64) ([]uint64, bizerr.BizError)
	ListClustersWithBackupByOrg(ctx context.Context, orgID uint64) ([]uint64, bizerr.BizError)
}

type LogBackupFilter struct {
	ClusterID *uint64
	ProjectID *uint64
	BucketID  *uint64

	BackupScheduleIDs []uint64
	CleanedUp         *bool
	Status            *entity.LogBackupStatus
}

type LogBackupRepo interface {
	Load(ctx context.Context, id uint64) (*entity.LogBackup, bizerr.BizError)
	Save(ctx context.Context, b *entity.LogBackup) bizerr.BizError

	CountByFilter(ctx context.Context, filter LogBackupFilter) (int64, bizerr.BizError)
	ListByFilter(ctx context.Context, filter LogBackupFilter) ([]*entity.LogBackup, bizerr.BizError)

	Get(ctx context.Context, ops ...LogBackupFilterOp) (*entity.LogBackup, bizerr.BizError)
	List(ctx context.Context, ops ...LogBackupFilterOp) ([]entity.LogBackup, bizerr.BizError)
	Count(ctx context.Context, ops ...LogBackupFilterOp) (int64, bizerr.BizError)
}

type LogBackupSizeRepo interface {
	Load(ctx context.Context, id entity.ID) (*entity.LogBackupSize, bizerr.BizError)
	Save(ctx context.Context, setting *entity.LogBackupSize) bizerr.BizError

	Get(ctx context.Context, ops ...LogBackupSizeFilterOp) (*entity.LogBackupSize, bizerr.BizError)
	List(ctx context.Context, ops ...LogBackupSizeFilterOp) ([]entity.LogBackupSize, bizerr.BizError)
}

type BackupSettingRepo interface {
	Load(ctx context.Context, id entity.ID) (*entity.BackupSetting, bizerr.BizError)
	LoadByClusterID(ctx context.Context, clusterID uint64) (*entity.BackupSetting, bizerr.BizError)
	Save(ctx context.Context, setting *entity.BackupSetting) bizerr.BizError

	GetByFilter(ctx context.Context, filter *BackupSettingFilter) (*entity.BackupSetting, bizerr.BizError)
	ListByFilter(ctx context.Context, filter *BackupSettingFilter) ([]*entity.BackupSetting, bizerr.BizError)

	Get(ctx context.Context, ops ...BackupSettingFilterOp) (*entity.BackupSetting, bizerr.BizError)
	List(ctx context.Context, ops ...BackupSettingFilterOp) ([]entity.BackupSetting, bizerr.BizError)
	Count(ctx context.Context, ops ...BackupSettingFilterOp) (int64, bizerr.BizError)
}

type BackupSettingFilter struct {
	ClusterID *uint64
	ProjectID *uint64
	TenantID  *uint64
}

type LogBackupTruncateRepo interface {
	Load(ctx context.Context, id uint64) (*entity.LogBackupTruncate, bizerr.BizError)
	Save(ctx context.Context, b *entity.LogBackupTruncate) bizerr.BizError
	List(ctx context.Context, ops ...LogBackupTruncateFilterOp) ([]entity.LogBackupTruncate, bizerr.BizError)
	Count(ctx context.Context, ops ...LogBackupTruncateFilterOp) (int64, bizerr.BizError)
}

type PitBackupSwitchRepo interface {
	Save(ctx context.Context, b *entity.PitBackupSwitch) bizerr.BizError
	Load(ctx context.Context, id uint64) (*entity.PitBackupSwitch, bizerr.BizError)
	LoadByClusterID(ctx context.Context, clusterID uint64) (*entity.PitBackupSwitch, bizerr.BizError)
	List(ctx context.Context, ops ...PitBackupSwitchFilterOp) ([]entity.PitBackupSwitch, bizerr.BizError)
}
type BackupExportFilter struct {
	TenantID  *uint64
	ProjectID *uint64
	ClusterID *uint64
	BackupID  *uint64
	Provider  *string
	Statuses  []entity.BackupExportStatus
}

type BackupExportRepo interface {
	Load(ctx context.Context, id uint64) (*entity.BackupExport, bizerr.BizError)
	Save(ctx context.Context, b *entity.BackupExport) bizerr.BizError

	ListByFilter(
		ctx context.Context,
		filter *BackupExportFilter,
		ops ...ListBackupExportOp,
	) ([]entity.BackupExport, bizerr.BizError)
	CountByFilter(ctx context.Context, filter *BackupExportFilter) (int64, bizerr.BizError)
}

type CompactBackupFilter struct {
	ClusterID   *uint64
	LogBackupID *uint64
	Status      *entity.CompactBackupStatus
	Statuses    []entity.CompactBackupStatus
	IDs         []entity.ID
}

type CompactBackupRepo interface {
	Load(ctx context.Context, id uint64) (*entity.CompactBackup, bizerr.BizError)
	Save(ctx context.Context, cb *entity.CompactBackup) bizerr.BizError

	ListByFilter(ctx context.Context, filter *CompactBackupFilter) ([]*entity.CompactBackup, bizerr.BizError)
	CountByFilter(ctx context.Context, filter *CompactBackupFilter) (int64, bizerr.BizError)

	// Specialized query methods
	GetRunningByCluster(ctx context.Context, clusterID uint64) (*entity.CompactBackup, bizerr.BizError)
	ListByLogBackup(ctx context.Context, logBackupID uint64) ([]*entity.CompactBackup, bizerr.BizError)
}
