;; lib/mod/tests/offenders.sx — Ext 7: repeat-offender escalation.

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

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

;; ── sanction? predicate ──

(mod-off-test! "hide is a sanction" (mod/sanction? "hide") true)
(mod-off-test! "remove is a sanction" (mod/sanction? "remove") true)
(mod-off-test! "ban is a sanction" (mod/sanction? "ban") true)
(mod-off-test! "keep is not a sanction" (mod/sanction? "keep") false)
(mod-off-test! "escalate is not a sanction" (mod/sanction? "escalate") false)

;; ── repeat-offender escalation over the audit log ──

(mod/reset!)
(mod/report "u1" "spammer" "this is spam")
(mod/report "u2" "spammer" "buy now offer")
(mod/report "u3" "spammer" "click here free money")
(mod/report "u4" "innocent" "fine post")

(mod-off-test!
  "no sanctions before any decision"
  (mod/subject-sanctions "spammer")
  0)

(define mod-off-d1 (mod/decide-escalating "r1" 2))
(mod-off-test!
  "first spam → hide (0 priors)"
  (get mod-off-d1 :action)
  "hide")
(mod-off-test!
  "one sanction recorded"
  (mod/subject-sanctions "spammer")
  1)

(define mod-off-d2 (mod/decide-escalating "r2" 2))
(mod-off-test!
  "second spam → hide (1 prior, below k=2)"
  (get mod-off-d2 :action)
  "hide")
(mod-off-test!
  "two sanctions recorded"
  (mod/subject-sanctions "spammer")
  2)

(define mod-off-d3 (mod/decide-escalating "r3" 2))
(mod-off-test!
  "third spam → ban (2 priors ≥ k)"
  (get mod-off-d3 :action)
  "ban")
(mod-off-test!
  "ban decision names repeat-offender rule"
  (get mod-off-d3 :rule)
  "repeat-offender-ban")
(mod-off-test!
  "ban proof records prior sanction count"
  (get (get mod-off-d3 :proof) :prior-sanctions)
  2)

;; ── different subjects accumulate independently ──

(define mod-off-d4 (mod/decide-escalating "r4" 2))
(mod-off-test!
  "innocent keep → not escalated"
  (get mod-off-d4 :action)
  "keep")
(mod-off-test!
  "innocent has no sanctions"
  (mod/subject-sanctions "innocent")
  0)
(mod-off-test!
  "repeat-offender? true for spammer at k=2"
  (mod/repeat-offender? "spammer" 2)
  true)
(mod-off-test!
  "repeat-offender? false for innocent at k=1"
  (mod/repeat-offender? "innocent" 1)
  false)

;; ── non-sanction decisions are never upgraded to ban ──
;; r5 is a clean post, but it is the 4th report about "spammer", so the
;; repeated-report rule escalates it. escalate is not a sanction, so it passes
;; through decide-escalating unchanged (never becomes :ban).

(mod/report "u5" "spammer" "a perfectly fine post")
(define mod-off-d5 (mod/decide-escalating "r5" 1))
(mod-off-test!
  "non-sanction (escalate) decision is not upgraded to ban"
  (get mod-off-d5 :action)
  "escalate")

(mod-off-test!
  "decide-escalating unknown id → nil"
  (mod/decide-escalating "r99" 2)
  nil)

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