package persistence

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

// RuntimeRecordEventRepairStore owns the exact operator replay transition for
// the dedicated metadata-driven record-event outbox. Normal dispatch and
// repair share the same deployment fence and queue identity.
type RuntimeRecordEventRepairStore struct {
	db          *sql.DB
	ownership   *RuntimeWorkOwnershipRepository
	scope       RuntimeWorkScope
	scopeDigest string
}

func NewRuntimeRecordEventRepairStore(db *sql.DB, ownership *RuntimeWorkOwnershipRepository, scope RuntimeWorkScope) (*RuntimeRecordEventRepairStore, error) {
	if db == nil || ownership == nil || scope.Validate() != nil {
		return nil, errors.New("runtime record-event repair: database, ownership authority, and work scope are required")
	}
	scopeDigest, err := scope.Digest()
	if err != nil {
		return nil, fmt.Errorf("runtime record-event repair: derive execution scope digest: %w", err)
	}
	return &RuntimeRecordEventRepairStore{db: db, ownership: ownership, scope: scope, scopeDigest: scopeDigest}, nil
}

func (s *RuntimeRecordEventRepairStore) CheckReady(ctx context.Context) error {
	if s == nil || s.db == nil || s.ownership == nil {
		return errors.New("runtime record-event repair: store is unavailable")
	}
	if err := s.ownership.CheckReady(ctx); err != nil {
		return err
	}
	if err := verifyRecordEventOutboxSchema(ctx, s.db); err != nil {
		return err
	}
	return verifyRecordEventOutboxScope(ctx, s.db, s.scope, s.scopeDigest)
}

func (s *RuntimeRecordEventRepairStore) Repair(ctx context.Context, repair RuntimePoisonRepair) (err error) {
	if s == nil || s.db == nil || s.ownership == nil {
		return errors.New("runtime record-event 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 := BeginPessimisticTx(ctx, s.db)
	if err != nil {
		return fmt.Errorf("runtime record-event 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 lastError string
	if err = tx.QueryRowContext(ctx, `SELECT RetryCount, COALESCE(ErrorMessage, '')
FROM _System_RecordEventOutbox
WHERE Id = ? AND Status = 'failed'
  AND ExecutionScopeDigest = ? AND OrgId = ? AND AppInstallationId = ? AND RuntimeCapabilityId = ?
FOR UPDATE`, repair.RowID, s.scopeDigest, s.scope.OrgID, s.scope.AppInstallationID, s.scope.RuntimeCapabilityID).Scan(&retryCount, &lastError); err != nil {
		return fmt.Errorf("runtime record-event repair: lock exact poison row: %w", err)
	}
	if !repair.Matches(retryCount, lastError) {
		return errors.New("runtime record-event repair: poison observation changed")
	}
	result, err := tx.ExecContext(ctx, `UPDATE _System_RecordEventOutbox
SET Status = 'pending', RetryCount = 0, ErrorMessage = NULL,
    ClaimToken = NULL,
    RepairId = ?, RepairReason = ?, RepairCount = RepairCount + 1,
    LastRepairAt = CURRENT_TIMESTAMP(6), LastModifiedDate = CURRENT_TIMESTAMP(6)
WHERE Id = ? AND Status = 'failed' AND RetryCount = ?
  AND ExecutionScopeDigest = ? AND OrgId = ? AND AppInstallationId = ? AND RuntimeCapabilityId = ?`,
		repair.RepairID, strings.TrimSpace(repair.Reason), repair.RowID, repair.ExpectedRetryCount,
		s.scopeDigest, s.scope.OrgID, s.scope.AppInstallationID, s.scope.RuntimeCapabilityID)
	if err != nil {
		return fmt.Errorf("runtime record-event repair: requeue exact poison row: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil || rows != 1 {
		return errors.New("runtime record-event repair: poison row changed before replay")
	}
	if err = tx.Commit(); err != nil {
		return fmt.Errorf("runtime record-event repair: commit exact replay: %w", err)
	}
	return nil
}
