package files

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/persistence"
)

// RuntimeUploadLifecycleRepairStore owns the operator-authored transition from
// one exactly observed poison row back to its original lifecycle validation.
// It shares the long-running worker's deployment fence but needs no ObjectStore
// access: an operator repairs the underlying immutable metadata/blob first,
// then this short-lived command makes that same obligation eligible for
// revalidation without changing its PREPARED versus BLOB_STORED identity.
type RuntimeUploadLifecycleRepairStore struct {
	db        *sql.DB
	ownership *persistence.RuntimeWorkOwnershipRepository
}

func NewRuntimeUploadLifecycleRepairStore(
	db *sql.DB,
	ownership *persistence.RuntimeWorkOwnershipRepository,
) (*RuntimeUploadLifecycleRepairStore, error) {
	if db == nil || ownership == nil {
		return nil, errors.New("runtime file upload repair: database and ownership authority are required")
	}
	return &RuntimeUploadLifecycleRepairStore{db: db, ownership: ownership}, nil
}

func (s *RuntimeUploadLifecycleRepairStore) CheckReady(ctx context.Context) error {
	if s == nil || s.db == nil || s.ownership == nil {
		return errors.New("runtime file upload repair: store is unavailable")
	}
	if err := s.ownership.CheckReady(ctx); err != nil {
		return err
	}
	return persistence.VerifyFileUploadOperationSchema(ctx, s.db)
}

func (s *RuntimeUploadLifecycleRepairStore) Repair(
	ctx context.Context,
	repair persistence.RuntimePoisonRepair,
) (err error) {
	if s == nil || s.db == nil || s.ownership == nil {
		return errors.New("runtime file upload repair: store is unavailable")
	}
	if err := repair.Validate(); err != nil {
		return err
	}
	if err := s.ownership.VerifyActive(ctx); err != nil {
		return err
	}
	tx, cleanup, err := persistence.BeginPessimisticTx(ctx, s.db)
	if err != nil {
		return fmt.Errorf("runtime file upload repair: begin: %w", err)
	}
	defer func() { _ = cleanup() }()
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		}
	}()
	if err = s.ownership.LockActive(ctx, tx); err != nil {
		return err
	}
	var retryCount int
	var poisonCode, lastError, lastRepairID string
	if err = tx.QueryRowContext(ctx, `SELECT RetryCount, COALESCE(ErrorMessage, ''),
       COALESCE(RepairId, ''), ErrorCode
FROM _System_FileUploadOperation
WHERE Id = ? AND State = 'FAILED' AND ErrorCode IN (?, ?)
FOR UPDATE`,
		repair.RowID,
		fileUploadRepairPoisoned,
		fileUploadPreparedRepairPoisoned,
	).Scan(
		&retryCount,
		&lastError,
		&lastRepairID,
		&poisonCode,
	); err != nil {
		return fmt.Errorf("runtime file upload repair: lock exact poison row: %w", err)
	}
	if !repair.Matches(retryCount, lastError) {
		return errors.New("runtime file upload repair: poison observation changed")
	}
	if lastRepairID == repair.RepairID {
		return errors.New("runtime file upload repair: repair identity was already used")
	}
	requeueState := persistence.FileUploadOperationBlobStored
	var requeueErrorCode any
	if poisonCode == fileUploadPreparedRepairPoisoned {
		requeueState = persistence.FileUploadOperationFailed
		requeueErrorCode = fileUploadPreparedCleanupClaimed
	}
	result, err := tx.ExecContext(ctx, `UPDATE _System_FileUploadOperation
SET State = ?, ErrorCode = ?, ErrorMessage = NULL,
    ExpiresAt = UTC_TIMESTAMP(6),
    RepairId = ?, RepairReason = ?, RepairCount = RepairCount + 1,
    LastRepairAt = UTC_TIMESTAMP(6), LastModifiedDate = UTC_TIMESTAMP(6)
WHERE Id = ? AND State = 'FAILED' AND ErrorCode = ? AND RetryCount = ?`,
		requeueState,
		requeueErrorCode,
		repair.RepairID,
		strings.TrimSpace(repair.Reason),
		repair.RowID,
		poisonCode,
		repair.ExpectedRetryCount,
	)
	if err != nil {
		return fmt.Errorf("runtime file upload repair: requeue exact poison row: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil || rows != 1 {
		return errors.New("runtime file upload repair: poison row changed before replay")
	}
	if err = tx.Commit(); err != nil {
		return fmt.Errorf("runtime file upload repair: commit exact replay: %w", err)
	}
	return nil
}
