package backup

import (
	"context"
	"errors"

	"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 manualBackupRestoreRepoImp struct {
	provider db.Provider
}

func NewManualBackupRestoreRepo(provider db.Provider) repo.ManualBackupRestoreRepo {
	return &manualBackupRestoreRepoImp{
		provider: provider,
	}
}

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

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

func (r *manualBackupRestoreRepoImp) Load(ctx context.Context, restoreID string, includeDeleted bool) (*entity.ManualBackupRestore, error) {
	if restoreID == "" {
		return nil, status.Errorf(codes.InvalidArgument, "restore id cannot be empty")
	}

	rdb := r.provider.ReadDB(ctx)
	if includeDeleted {
		rdb = rdb.Unscoped()
	}
	var model schema.ManualBackupRestore
	if err := rdb.Preload("Scale", func(db *gorm.DB) *gorm.DB {
		return db.Unscoped()
	}).Where("restore_id = ?", restoreID).First(&model).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Errorf(codes.NotFound, "ManualBackupRestore with restore id %s not found", restoreID)
		}
		return nil, status.Errorf(codes.Internal, "failed to load ManualBackupRestore: %v", err)
	}

	return &entity.ManualBackupRestore{
		ManualBackupRestore: model,
	}, nil
}

func (r *manualBackupRestoreRepoImp) List(ctx context.Context, filter *repo.ManualBackupRestoreFilter) ([]*entity.ManualBackupRestore, error) {
	rdb := r.provider.ReadDB(ctx)
	if filter != nil && filter.IncludeDeleted {
		rdb = rdb.Unscoped()
	}

	query := rdb.Model(&schema.ManualBackupRestore{})
	if filter != nil {
		if len(filter.ExcludeRestoreID) > 0 {
			query = query.Where("restore_id NOT IN ?", filter.ExcludeRestoreID)
		}
		if len(filter.IncludeStatuses) > 0 {
			query = query.Where("status IN ?", filter.IncludeStatuses)
		}
		if filter.SourceSharedPoolID != nil {
			query = query.Where("source_shared_pool_id = ?", filter.SourceSharedPoolID)
		}
		if filter.TargetSharedPoolID != nil {
			query = query.Where("target_shared_pool_id = ?", filter.TargetSharedPoolID)
		}
		if filter.StartTime != nil {
			query = query.Where("created_at >= ?", filter.StartTime)
		}
	}

	var models []*schema.ManualBackupRestore
	if err := query.Preload("Scale", func(db *gorm.DB) *gorm.DB {
		return db.Unscoped()
	}).Find(&models).Error; err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list ManualBackupRestore: %v", err)
	}

	result := make([]*entity.ManualBackupRestore, 0, len(models))
	for _, model := range models {
		result = append(result, &entity.ManualBackupRestore{ManualBackupRestore: *model})
	}
	return result, nil
}

func (r *manualBackupRestoreRepoImp) Delete(ctx context.Context, restoreID string) error {
	if restoreID == "" {
		return status.Errorf(codes.InvalidArgument, "restore id cannot be empty")
	}

	wdb := r.provider.WriteDB(ctx)
	if err := wdb.Where("restore_id = ?", restoreID).Delete(&schema.ManualBackupRestore{}).Error; err != nil {
		return status.Errorf(codes.Internal, "failed to delete ManualBackupRestore: %v", err)
	}
	return nil
}

func (r *manualBackupRestoreRepoImp) SetStatus(ctx context.Context, restoreID, statusValue string) error {
	return r.SetStatusWithFailure(ctx, restoreID, statusValue, nil)
}

func (r *manualBackupRestoreRepoImp) SetStatusWithFailure(ctx context.Context, restoreID, statusValue string, failure *entity.RestoreFailure) error {
	if restoreID == "" {
		return status.Errorf(codes.InvalidArgument, "restore id cannot be empty")
	}
	if statusValue == "" {
		return status.Errorf(codes.InvalidArgument, "status cannot be empty")
	}

	restore, err := r.Load(ctx, restoreID, false)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to load ManualBackupRestore: %v", err)
	}
	changed := restore.Status != statusValue
	if changed {
		restore.Status = statusValue
	}
	if failure != nil {
		if restore.FailureSource != failure.Source || restore.FailureCode != failure.Code || restore.FailureMessage != failure.Message {
			changed = true
		}
		restore.FailureSource = failure.Source
		restore.FailureCode = failure.Code
		restore.FailureMessage = failure.Message
	}
	if !changed {
		return nil
	}
	if err := r.Save(ctx, restore); err != nil {
		return status.Errorf(codes.Internal, "failed to save ManualBackupRestore: %v", err)
	}
	return nil
}

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

	restore, err := r.Load(ctx, restoreID, false)
	if err != nil {
		return false, status.Errorf(codes.Internal, "failed to load ManualBackupRestore: %v", err)
	}

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