package restore

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

func NewClassicRestoreRepo(provider db.Provider) repo.ClassicRestoreRepo {
	return &classicRestoreRepoImp{
		provider: provider,
	}
}

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

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

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

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

	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, "ClassicRestore with restore id %s not found", restoreID)
		}
		return nil, status.Errorf(codes.Internal, "failed to load ClassicRestore: %v", err)
	}

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

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

	wdb := r.provider.WriteDB(ctx)

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

	return nil
}

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

	restore, err := r.Load(ctx, restoreID, false)
	if err != nil {
		return false, status.Errorf(codes.Internal, "failed to load ClassicRestore: %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 ClassicRestore: %v", err)
	}
	return true, nil
}

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

func (r *classicRestoreRepoImp) SetStatusWithFailure(ctx context.Context, restoreID, restoreStatus string, failure *entity.RestoreFailure) error {
	if restoreID == "" {
		return status.Errorf(codes.InvalidArgument, "ClassicRestore 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 ClassicRestore: %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 ClassicRestore: %v", err)
	}

	return nil
}
