package dbutil

// Database access should be done through this file.
import (
	"fmt"
	"time"

	"github.com/pingcap/errors"
	entity "github.com/tidbcloud/core-svc/model"
	"gorm.io/gorm"
)

// fetches the entities of a QuerySet
func BackupScheduleFetch(qs entity.BackupScheduleQuerySet) ([]entity.BackupSchedule, error) {
	var backupSchedules []entity.BackupSchedule
	err := qs.All(&backupSchedules)
	return backupSchedules, errors.Annotate(err, "Fetch BackupSchedules")
}

// fetches the entities of a QuerySet
func BackupFetch(qs entity.BackupQuerySet) ([]entity.Backup, error) {
	var backups []entity.Backup
	err := qs.All(&backups)
	return backups, errors.Annotate(err, "Fetch Backups")
}

func ClusterBackups(tx *gorm.DB, clusterID entity.ID) ([]entity.Backup, error) {
	var backups []entity.Backup
	backupSchedules, err := BackupScheduleFetch(ClusterBackupSchedules(tx, clusterID))
	if err != nil {
		return nil, err
	}
	for _, schedule := range backupSchedules {
		s := schedule
		backupList, err := BackupFetch(ScheduleBackups(tx, schedule.ID))
		if err != nil {
			return nil, errors.Annotate(err, "Backups cluster query")
		}
		for _, backup := range backupList {
			backup.BackupSchedule = &s
			backup.BackupScheduleID = s.ID
			backups = append(backups, backup)
		}
	}
	return backups, nil
}

func DeleteBackup(tx *gorm.DB, orgID, projectID, clusterID, backupID entity.ID) error {
	backup, err := GetBackupByID(tx, orgID, projectID, clusterID, backupID)
	if err != nil {
		return err
	}
	now := time.Now()
	return tx.Model(backup).Update("deleted_at", now).Error
}

func DeleteBackupsByClusterIDs(tx *gorm.DB, clusterIDs []entity.ID) error {
	schedules, err := GetBackupSchedulesByClusterIDs(tx, clusterIDs)
	if err != nil {
		return errors.Annotate(err, "get backupSchedules by clusterIds")
	}
	if len(schedules) == 0 {
		return nil
	}

	scheduleIDs := make([]entity.ID, 0, len(schedules))
	for _, each := range schedules {
		scheduleIDs = append(scheduleIDs, each.ID)
	}
	now := time.Now()
	return tx.Model(&entity.Backup{}).Where("backup_schedule_id in (?) AND deleted_at is null", scheduleIDs).Update("deleted_at", now).Error
}

func DeleteDeletedClusterBackups(tx *gorm.DB, orgID entity.ID, projectID entity.ID, clusterID entity.ID, backupType entity.BackupType) error {
	var backupSchedules []entity.BackupSchedule
	var err error
	if backupType == entity.BackupType_All {
		backupSchedules, err = BackupScheduleFetch(ClusterBackupSchedules(tx, clusterID))
		if err != nil {
			return err
		}
	} else {
		backupSchedules, err = BackupScheduleFetch(ClusterBackupSchedules(tx, clusterID).TypeEq(backupType))
		if err != nil {
			return err
		}
	}
	for _, schedule := range backupSchedules {
		backups, err := BackupFetch(ScheduleBackups(tx, schedule.ID))
		if err != nil {
			return errors.Annotate(err, "backups in progress query")
		}
		for _, backup := range backups {
			if backup.DeletedAt == nil {
				if err = DeleteBackup(tx, orgID, projectID, clusterID, backup.ID); err != nil {
					return errors.Annotate(err, fmt.Sprintf("failed to delete backup %d", backup.ID))
				}
			}
		}
	}

	return nil
}

// GetBackupByID：used by service API
func GetBackupByID(tx *gorm.DB, orgID, projectID, clusterID, id entity.ID) (*entity.Backup, error) {
	var backup entity.Backup
	err := entity.NewBackupQuerySet(tx.Preload("BackupSchedule.Cluster")).PreloadBackupSchedule().IDEq(id).One(&backup)
	if err != nil {
		return &backup, errors.Annotate(TagNotFound(err), "GetBackupByID")
	}
	err = CheckResourceOrgIDAndProjectID(orgID, backup.BackupSchedule.Cluster.TenantID, projectID, backup.BackupSchedule.Cluster.ProjectID)
	if err != nil {
		return &backup, err
	}
	if backup.BackupSchedule.ClusterID != clusterID {
		return &backup, errors.Errorf("cluster %d does not have backup %d", clusterID, id)
	}
	return &backup, nil
}
