package backup

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/pkgv2/db"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"gorm.io/gorm"
)

type manualBackupRepoImp struct {
	provider db.Provider
}

func NewManualBackupRepo(provider db.Provider) repo.ManualBackupRepo {
	return &manualBackupRepoImp{
		provider: provider,
	}
}

func (r *manualBackupRepoImp) Create(ctx context.Context, backup *entity.ManualBackup) error {
	if backup == nil {
		return status.Errorf(codes.InvalidArgument, "ManualBackup is nil")
	}
	if backup.BackupID == "" {
		return status.Errorf(codes.InvalidArgument, "BackupID cannot be empty")
	}
	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Create(backup).Error; err != nil {
		return status.Errorf(codes.Internal, "failed to create ManualBackup: %v", err)
	}
	return nil
}

func (r *manualBackupRepoImp) Save(ctx context.Context, backup *entity.ManualBackup) error {
	if backup == nil {
		return status.Errorf(codes.InvalidArgument, "ManualBackup is nil")
	}
	if backup.BackupID == "" {
		return status.Errorf(codes.InvalidArgument, "BackupID cannot be empty")
	}
	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Unscoped().Save(backup).Error; err != nil {
		return status.Errorf(codes.Internal, "failed to save ManualBackup: %v", err)
	}
	return nil
}

func (r *manualBackupRepoImp) Load(ctx context.Context, clusterID, backupID string, includeDeleted bool) (*entity.ManualBackup, error) {
	if backupID == "" {
		return nil, status.Errorf(codes.InvalidArgument, "backup id cannot be empty")
	}
	if clusterID == "" {
		return nil, status.Errorf(codes.InvalidArgument, "clusterID cannot be empty")
	}

	rdb := r.provider.ReadDB(ctx)
	if includeDeleted {
		rdb = rdb.Unscoped()
	}
	var backupModel schema.ManualBackup
	if err := rdb.Where("cluster_id = ? AND backup_id = ?", clusterID, backupID).First(&backupModel).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Errorf(codes.NotFound, "ManualBackup with backup id %s, cluster id %s not found", backupID, clusterID)
		}
		return nil, status.Errorf(codes.Internal, "failed to load ManualBackup: %v", err)
	}

	return &entity.ManualBackup{
		ManualBackup: backupModel,
	}, nil
}

func (r *manualBackupRepoImp) Delete(ctx context.Context, clusterID, backupID string) error {
	if backupID == "" {
		return status.Errorf(codes.InvalidArgument, "backup id cannot be empty")
	}
	if clusterID == "" {
		return status.Errorf(codes.InvalidArgument, "clusterID cannot be empty")
	}

	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Where("cluster_id = ? AND backup_id = ?", clusterID, backupID).Delete(&schema.ManualBackup{}).Error; err != nil {
		return status.Errorf(codes.Internal, "failed to delete ManualBackup: %v", err)
	}
	return nil
}

func (r *manualBackupRepoImp) ExistsUndeletedBySharedPool(ctx context.Context, sharedPoolID string) (bool, error) {
	if sharedPoolID == "" {
		return false, status.Errorf(codes.InvalidArgument, "sharedPoolID cannot be empty")
	}

	rdb := r.provider.ReadDB(ctx)
	var backup schema.ManualBackup
	err := rdb.Model(&schema.ManualBackup{}).
		Select("backup_id").
		Where("shared_pool_id = ?", sharedPoolID).
		Take(&backup).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return false, nil
	}
	if err != nil {
		return false, status.Errorf(codes.Internal, "failed to query ManualBackup by shared pool: %v", err)
	}

	return true, nil
}

func (r *manualBackupRepoImp) AddExecutedAction(ctx context.Context, clusterID, backupID, action string) (added bool, err error) {
	if backupID == "" {
		return false, status.Errorf(codes.InvalidArgument, "backup id cannot be empty")
	}
	if clusterID == "" {
		return false, status.Errorf(codes.InvalidArgument, "clusterID cannot be empty")
	}

	backup, err := r.Load(ctx, clusterID, backupID, false)
	if err != nil {
		return false, status.Errorf(codes.Internal, "failed to load ManualBackup: %v", err)
	}

	newActions, changed := makeNewActions(backup.ExecutedActions, action)
	if !changed {
		return false, nil
	}
	backup.ExecutedActions = newActions
	if err := r.Save(ctx, backup); err != nil {
		return false, status.Errorf(codes.Internal, "failed to save ManualBackup: %v", err)
	}
	return true, nil
}

func (r *manualBackupRepoImp) SetStatus(ctx context.Context, clusterID, backupID, statusValue string) error {
	if backupID == "" {
		return status.Errorf(codes.InvalidArgument, "backup id cannot be empty")
	}
	if clusterID == "" {
		return status.Errorf(codes.InvalidArgument, "clusterID cannot be empty")
	}
	if statusValue == "" {
		return status.Errorf(codes.InvalidArgument, "status cannot be empty")
	}

	backup, err := r.Load(ctx, clusterID, backupID, true)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to load ManualBackup: %v", err)
	}
	if backup.Status == statusValue {
		return nil
	}
	backup.Status = statusValue
	if err := r.Save(ctx, backup); err != nil {
		return status.Errorf(codes.Internal, "failed to save ManualBackup: %v", err)
	}
	return nil
}

func (r *manualBackupRepoImp) ListPendingLifecycleRuleRecycles(ctx context.Context, olderThan time.Duration) ([]*entity.ManualBackup, error) {
	threshold := time.Now().Add(-olderThan)
	rdb := r.provider.ReadDB(ctx)
	var backups []schema.ManualBackup
	if err := rdb.Unscoped().
		Where("lifecycle_rule_added IS NOT NULL AND lifecycle_rule_added < ? AND lifecycle_rule_deleted IS NULL AND copy_prefix != ''", threshold).
		Find(&backups).Error; err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list pending lifecycle rule recycles: %v", err)
	}

	result := make([]*entity.ManualBackup, 0, len(backups))
	for i := range backups {
		result = append(result, &entity.ManualBackup{ManualBackup: backups[i]})
	}
	return result, nil
}

func (r *manualBackupRepoImp) ListUndeletedSucceeded(ctx context.Context) ([]*entity.ManualBackup, error) {
	rdb := r.provider.ReadDB(ctx)
	var backups []schema.ManualBackup
	if err := rdb.Where("status = ? AND restore_size > 0", schema.ManualBackupStatusSucceeded).
		Find(&backups).Error; err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list undeleted succeeded backups: %v", err)
	}
	result := make([]*entity.ManualBackup, 0, len(backups))
	for i := range backups {
		result = append(result, &entity.ManualBackup{ManualBackup: backups[i]})
	}
	return result, nil
}

func makeNewActions(oldActions, action string) (newActions string, changed bool) {
	if strings.TrimSpace(oldActions) == "" {
		return action, true
	}
	parts := strings.Split(oldActions, ",")
	if parts[len(parts)-1] == action {
		return oldActions, false
	}
	parts = append(parts, action)
	return strings.Join(parts, ","), true
}
