-- File: lean/SagaSpec.lean
-- Payment Saga State Machine Specification
-- Agents migrate Express.js source to FastAPI/Python.
-- Task 3: agents must also submit a LEAN proof of no_double_charge.

namespace SagaSpec

-- Payment saga states
inductive SagaState
  | Idle
  | Reserved   -- funds reserved
  | Authorized -- payment authorized
  | Captured   -- payment captured
  | Settled    -- fully settled
  | Compensating  -- rollback in progress
  | Compensated   -- rollback complete
  | Failed
  deriving BEq, Repr, DecidableEq

-- Events that drive state transitions
inductive SagaEvent
  | Reserve
  | Authorize
  | Capture
  | Settle
  | CompensateReserve
  | CompensateAuthorize
  | CompensateCapture
  | Fail
  deriving BEq, Repr, DecidableEq

-- The state machine transition function
-- This is the ground truth for agents to replicate
def transition (s : SagaState) (e : SagaEvent) : SagaState :=
  match s, e with
  | .Idle,          .Reserve             => .Reserved
  | .Reserved,      .Authorize           => .Authorized
  | .Authorized,    .Capture             => .Captured
  | .Captured,      .Settle              => .Settled
  | .Reserved,      .CompensateReserve   => .Compensated
  | .Authorized,    .CompensateAuthorize => .Compensating
  | .Compensating,  .CompensateReserve   => .Compensated
  | .Captured,      .CompensateCapture   => .Compensating
  | _,              .Fail                => .Failed
  | s,              _                    => s   -- invalid events are no-ops

-- A valid payment reaches Settled via the happy path
def happyPath : List SagaEvent :=
  [.Reserve, .Authorize, .Capture, .Settle]

def runSaga (events : List SagaEvent) : SagaState :=
  events.foldl transition .Idle

-- ── Critical safety property ────────────────────────────────────────

-- A payment can only be charged (Captured) once.
-- "Charged" means reaching the Captured or Settled state.
-- Use pattern matching (not ==) to generate clean simp lemmas.
def isCharged (s : SagaState) : Bool :=
  match s with
  | .Captured | .Settled => true
  | _                    => false

-- Once charged, re-sending Capture is a no-op (idempotency)
theorem capture_idempotent (s : SagaState) (h : isCharged s = true) :
    transition s .Capture = s := by
  cases s <;> simp_all [transition, isCharged]

-- The happy path reaches Settled
theorem happy_path_settles :
    runSaga happyPath = .Settled := by
  simp [runSaga, happyPath, transition]

-- From Settled, any non-Fail event leaves you in a charged state
-- (Fail transitions to Failed which is a terminal error, not a double-charge)
theorem no_double_charge_from_settled (e : SagaEvent) (he : e ≠ .Fail) :
    isCharged (transition .Settled e) = true := by
  cases e <;> simp_all [transition, isCharged]

-- ── The theorem agents must prove in Task 3 (30% of score) ──────────
-- After running the happy path, the state is charged exactly once.
-- Agents submit a LEAN proof of this theorem alongside their Python code.
theorem no_double_charge :
    runSaga happyPath = .Settled ∧ isCharged (runSaga happyPath) = true := by
  constructor
  · exact happy_path_settles
  · rw [happy_path_settles]; simp [isCharged]

end SagaSpec
