;; lib/mod/tests/escalation.sx — Phase 3: lifecycle state machine + escalation.

(define mod-esc-count 0)
(define mod-esc-pass 0)
(define mod-esc-fail 0)
(define mod-esc-failures (list))

(define
  mod-esc-test!
  (fn
    (name got expected)
    (begin
      (set! mod-esc-count (+ mod-esc-count 1))
      (if
        (= got expected)
        (set! mod-esc-pass (+ mod-esc-pass 1))
        (begin
          (set! mod-esc-fail (+ mod-esc-fail 1))
          (append!
            mod-esc-failures
            (str name "\n    expected: " expected "\n    got:      " got)))))))

;; ── transition table guard ──

(mod-esc-test!
  "open → triaged allowed"
  (mod/lc-can-transition? "open" "triaged")
  true)
(mod-esc-test!
  "triaged → decided allowed"
  (mod/lc-can-transition? "triaged" "decided")
  true)
(mod-esc-test!
  "decided → appealed allowed"
  (mod/lc-can-transition? "decided" "appealed")
  true)
(mod-esc-test!
  "appealed → final allowed"
  (mod/lc-can-transition? "appealed" "final")
  true)
(mod-esc-test!
  "open → decided rejected"
  (mod/lc-can-transition? "open" "decided")
  false)
(mod-esc-test!
  "triaged → final rejected"
  (mod/lc-can-transition? "triaged" "final")
  false)
(mod-esc-test!
  "final is terminal"
  (mod/lc-can-transition? "final" "open")
  false)

;; ── initial state ──

(define
  mod-esc-c0
  (mod/mk-case (mod/mk-report "r1" "alice" "bob" "this is spam")))
(mod-esc-test! "new case is open" (mod/case-state mod-esc-c0) "open")
(mod-esc-test! "new case has no decision" (mod/case-decision mod-esc-c0) nil)

;; ── auto-tier: spam triages + resolves to decided/hide ──

(define
  mod-esc-spam-rep
  (list (mod/mk-report "r1" "alice" "bob" "this is spam")))
(define
  mod-esc-t1
  (mod/case-triage mod-esc-c0 mod-esc-spam-rep mod/default-rules))
(mod-esc-test! "spam triaged" (mod/case-state mod-esc-t1) "triaged")
(mod-esc-test! "spam triage tier auto" (mod/case-tier mod-esc-t1) "auto")
(mod-esc-test! "spam triage action hide" (mod/case-action mod-esc-t1) "hide")

(define mod-esc-r1 (mod/case-resolve mod-esc-t1))
(mod-esc-test!
  "auto resolve → decided"
  (mod/case-state mod-esc-r1)
  "decided")
(mod-esc-test!
  "decision preserved through resolve"
  (mod/case-action mod-esc-r1)
  "hide")

;; ── illegal transition flags :error, leaves state ──

(define mod-esc-bad (mod/case-finalize mod-esc-c0))
(mod-esc-test!
  "finalize from open is illegal"
  (mod/case-state mod-esc-bad)
  "open")
(mod-esc-test!
  "illegal transition sets error"
  (nil? (mod/case-error mod-esc-bad))
  false)

;; ── human-tier: repeated report escalates, resolve blocked, review decides ──

(define mod-esc-rep-r (mod/mk-report "r3" "ann" "dave" "off-topic"))
(define mod-esc-rep-reports (list mod-esc-rep-r mod-esc-rep-r mod-esc-rep-r))
(define mod-esc-rep-c0 (mod/mk-case mod-esc-rep-r))
(define
  mod-esc-rep-t
  (mod/case-triage mod-esc-rep-c0 mod-esc-rep-reports mod/default-rules))

(mod-esc-test!
  "repeated triage action escalate"
  (mod/case-action mod-esc-rep-t)
  "escalate")
(mod-esc-test!
  "repeated triage tier human"
  (mod/case-tier mod-esc-rep-t)
  "human")
(mod-esc-test!
  "repeated still triaged after triage"
  (mod/case-state mod-esc-rep-t)
  "triaged")

(define mod-esc-rep-block (mod/case-resolve mod-esc-rep-t))
(mod-esc-test!
  "auto-resolve blocked on human tier (state unchanged)"
  (mod/case-state mod-esc-rep-block)
  "triaged")
(mod-esc-test!
  "blocked resolve sets error"
  (nil? (mod/case-error mod-esc-rep-block))
  false)

(define
  mod-esc-rep-rev
  (mod/case-review
    mod-esc-rep-t
    "confirmed-abuse"
    "human"
    mod-esc-rep-reports
    mod/default-rules))
(mod-esc-test!
  "human review → decided"
  (mod/case-state mod-esc-rep-rev)
  "decided")
(mod-esc-test!
  "human review action remove"
  (mod/case-action mod-esc-rep-rev)
  "remove")
(mod-esc-test!
  "review attached evidence to report"
  (len (mod/report-evidence (mod/case-report mod-esc-rep-rev)))
  1)

(define mod-esc-rep-final (mod/case-finalize mod-esc-rep-rev))
(mod-esc-test!
  "review case finalizes"
  (mod/case-state mod-esc-rep-final)
  "final")

;; ── appeal overrides a prior decision ──

(define
  mod-esc-ap-c0
  (mod/mk-case (mod/mk-report "r5" "u" "v" "buy now spam")))
(define mod-esc-ap-rep (list (mod/mk-report "r5" "u" "v" "buy now spam")))
(define
  mod-esc-ap-t
  (mod/case-triage mod-esc-ap-c0 mod-esc-ap-rep mod/default-rules))
(define mod-esc-ap-d (mod/case-resolve mod-esc-ap-t))

(mod-esc-test!
  "appeal precondition decided/hide"
  (mod/case-action mod-esc-ap-d)
  "hide")

(define
  mod-esc-ap-appealed
  (mod/case-appeal
    mod-esc-ap-d
    "exonerated"
    "moderator"
    mod-esc-ap-rep
    mod/default-rules))
(mod-esc-test!
  "appeal → appealed state"
  (mod/case-state mod-esc-ap-appealed)
  "appealed")
(mod-esc-test!
  "appeal overrides hide → keep"
  (mod/case-action mod-esc-ap-appealed)
  "keep")
(mod-esc-test!
  "appeal recorded via exonerated-keep rule"
  (get (mod/case-decision mod-esc-ap-appealed) :rule)
  "exonerated-keep")

(define mod-esc-ap-final (mod/case-finalize mod-esc-ap-appealed))
(mod-esc-test! "appealed → final" (mod/case-state mod-esc-ap-final) "final")

;; ── history records the full traversal ──

(mod-esc-test!
  "full lifecycle history length 4 (triage,resolve,appeal,finalize)"
  (len (mod/case-history mod-esc-ap-final))
  4)
(mod-esc-test!
  "first history step open→triaged"
  (get (first (mod/case-history mod-esc-ap-final)) :to)
  "triaged")
(mod-esc-test!
  "last history step → final"
  (get (nth (mod/case-history mod-esc-ap-final) 3) :to)
  "final")

;; ── api-level lifecycle façade ──

(mod/reset!)
(mod/report "alice" "bob" "this is spam")
(mod/report "carol" "dave" "off-topic")
(mod/report "carol" "dave" "off-topic")
(mod/report "carol" "dave" "off-topic")

(mod-esc-test!
  "api: case opens at open"
  (mod/case-state (mod/case-of "r1"))
  "open")

(define mod-esc-api-t1 (mod/triage "r1"))
(mod-esc-test!
  "api: triage spam → triaged"
  (mod/case-state mod-esc-api-t1)
  "triaged")
(mod-esc-test!
  "api: triage spam action hide"
  (mod/case-action mod-esc-api-t1)
  "hide")

(define mod-esc-api-r1 (mod/resolve "r1"))
(mod-esc-test!
  "api: resolve → decided"
  (mod/case-state mod-esc-api-r1)
  "decided")
(mod-esc-test!
  "api: resolve logged decision"
  (len (mod/audit "r1"))
  1)

(define mod-esc-api-app (mod/appeal "r1" "exonerated" "mod"))
(mod-esc-test!
  "api: appeal → appealed"
  (mod/case-state mod-esc-api-app)
  "appealed")
(mod-esc-test!
  "api: appeal overrides → keep"
  (mod/case-action mod-esc-api-app)
  "keep")
(mod-esc-test!
  "api: appeal logged second decision"
  (len (mod/audit "r1"))
  2)
(mod-esc-test!
  "api: finalize → final"
  (mod/case-state (mod/finalize "r1"))
  "final")

;; r4 is the 3rd report about dave → escalates via the human tier
(define mod-esc-api-t4 (mod/triage "r4"))
(mod-esc-test!
  "api: repeated triage escalates (human tier)"
  (mod/case-tier mod-esc-api-t4)
  "human")
(define mod-esc-api-blk (mod/resolve "r4"))
(mod-esc-test!
  "api: escalated resolve blocked"
  (mod/case-state mod-esc-api-blk)
  "triaged")
(define mod-esc-api-rev (mod/review "r4" "confirmed-abuse" "human"))
(mod-esc-test!
  "api: review → decided/remove"
  (mod/case-action mod-esc-api-rev)
  "remove")
(mod-esc-test! "api: unknown id → nil" (mod/triage "r99") nil)

(define mod-escalation-tests-run! (fn () {:failures mod-esc-failures :total mod-esc-count :passed mod-esc-pass :failed mod-esc-fail}))
