package restore

import (
	"context"
	"errors"
	"strings"

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

func NewLwRestoreRepo(provider db.Provider) repo.LightweightRestoreRepo {
	return &lwRestoreRepoImp{
		provider: provider,
	}
}

func (r *lwRestoreRepoImp) Create(ctx context.Context, restore *entity.LightweightRestore) error {
	if restore == nil {
		return status.Errorf(codes.InvalidArgument, "LightweightRestore 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 LightweightRestore: %v", err)
	}
	return nil
}

func (r *lwRestoreRepoImp) Save(ctx context.Context, restore *entity.LightweightRestore) error {
	if restore == nil {
		return status.Errorf(codes.InvalidArgument, "LightweightRestore 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 LightweightRestore: %v", err)
	}
	return nil
}

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

	rdb := r.provider.ReadDB(ctx)
	if includeDeleted {
		rdb = rdb.Unscoped()
	}
	var restoreModel schema.LightweightRestore

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

	return &entity.LightweightRestore{
		LightweightRestore: restoreModel,
	}, nil
}

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

	wdb := r.provider.WriteDB(ctx)

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

	return nil
}

func (r *lwRestoreRepoImp) List(ctx context.Context, filter *repo.LightweightRestoreFilter) ([]*entity.LightweightRestore, error) {
	rdb := r.provider.ReadDB(ctx)
	if filter.IncludeDeleted {
		rdb = rdb.Unscoped()
	}

	query := rdb.Model(&schema.LightweightRestore{})
	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)
	}
	if filter.ScaleMade != nil {
		if *filter.ScaleMade {
			query = query.Where("scale_id IS NOT NULL")
		} else {
			query = query.Where("scale_id IS NULL")
		}
	}

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

	var result []*entity.LightweightRestore
	for _, restore := range restores {
		result = append(result, &entity.LightweightRestore{
			LightweightRestore: *restore,
		})
	}
	return result, nil
}

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

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

	newActions, changed := makeNewActions(restore.ExecutedActions, action)
	if !changed {
		return false, nil
	}
	restore.ExecutedActions = newActions
	err = r.Save(ctx, restore)
	if err != nil {
		return false, status.Errorf(codes.Internal, "failed to save LightweightRestore: %v", err)
	}
	return true, 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)
	newActions = strings.Join(parts, ",")
	return newActions, true
}

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

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

	if restoreStatus == "" {
		return status.Errorf(codes.InvalidArgument, "restore status cannot be empty")
	}

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

	changed := restore.Status != restoreStatus
	if changed {
		restore.Status = restoreStatus
	}
	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
	}
	err = r.Save(ctx, restore)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to save LightweightRestore: %v", err)
	}

	return nil
}
