;; lib/dream/tests/flash.sx — codec + read-after-write across requests.

(define dream-fl-pass 0)
(define dream-fl-fail 0)
(define dream-fl-fails (list))

(define
  dream-fl-test
  (fn
    (name actual expected)
    (if
      (= actual expected)
      (set! dream-fl-pass (+ dream-fl-pass 1))
      (begin
        (set! dream-fl-fail (+ dream-fl-fail 1))
        (append! dream-fl-fails {:name name :actual actual :expected expected})))))

;; ── codec ──────────────────────────────────────────────────────────
(dream-fl-test "encode one" (dr/flash-encode (list {:message "saved" :category "info"})) "info|saved")
(dream-fl-test
  "encode two"
  (dr/flash-encode (list {:message "a" :category "info"} {:message "b" :category "error"}))
  "info|a~error|b")
(dream-fl-test "decode one" (dr/flash-decode "info|saved") (list {:message "saved" :category "info"}))
(dream-fl-test "decode empty" (dr/flash-decode "") (list))
(dream-fl-test
  "roundtrip special chars"
  (dr/flash-decode (dr/flash-encode (list {:message "a~b%c" :category "x|y"})))
  (list {:message "a~b%c" :category "x|y"}))
(dream-fl-test "escape pipe" (dr/flash-encode (list {:message "a|b" :category "c"})) "c|a%7Cb")

;; extract a cookie value from a Set-Cookie string
(define
  dream-fl-cookie-val
  (fn
    (setc)
    (let
      ((after (substr setc (+ (index-of setc "=") 1))))
      (substr after 0 (index-of after ";")))))

;; ── read-after-write across requests ───────────────────────────────
(define
  dream-fl-set-h
  (fn
    (req)
    (begin (dream-add-flash-message req "info" "Saved!") (dream-text "done"))))
(define dream-fl-set-app (dream-flash dream-fl-set-h))

;; request 1: add a flash, no incoming -> sets the flash cookie
(define
  dream-fl-r1
  (dream-fl-set-app (dream-request "POST" "/save" {} "")))
(dream-fl-test "writer body" (dream-resp-body dream-fl-r1) "done")
(dream-fl-test
  "writer sets flash cookie"
  (len (dream-resp-cookies dream-fl-r1))
  1)
(dream-fl-test
  "writer has no incoming"
  (dream-flash-messages
    (assoc (dream-request "GET" "/" {} "") :dream-flash {:box (dr/flash-box) :incoming (list)}))
  (list))

;; request 2: carries the flash cookie -> handler reads it, cookie cleared
(define
  dream-fl-cval
  (dream-fl-cookie-val (first (dream-resp-cookies dream-fl-r1))))
(define
  dream-fl-read-h
  (fn
    (req)
    (let
      ((msgs (dream-flash-messages req)))
      (dream-text
        (if (empty? msgs) "none" (dream-flash-message (first msgs)))))))
(define dream-fl-read-app (dream-flash dream-fl-read-h))
(define
  dream-fl-r2
  (dream-fl-read-app (dream-request "GET" "/" {:Cookie (str "dream.flash=" dream-fl-cval)} "")))
(dream-fl-test "reader sees message" (dream-resp-body dream-fl-r2) "Saved!")
(dream-fl-test
  "reader clears cookie (Max-Age=0)"
  (contains? (first (dream-resp-cookies dream-fl-r2)) "Max-Age=0")
  true)

;; request 3: no flash cookie -> nothing to read, no cookie set
(define
  dream-fl-r3
  (dream-fl-read-app (dream-request "GET" "/" {} "")))
(dream-fl-test "no flash -> none" (dream-resp-body dream-fl-r3) "none")
(dream-fl-test
  "no flash -> no cookie"
  (len (dream-resp-cookies dream-fl-r3))
  0)

;; ── multiple categories ────────────────────────────────────────────
(define
  dream-fl-multi-h
  (fn
    (req)
    (begin
      (dream-add-flash-message req "info" "i1")
      (dream-add-flash-message req "error" "e1")
      (dream-add-flash-message req "info" "i2")
      (dream-text "ok"))))
(define
  dream-fl-multi-r1
  ((dream-flash dream-fl-multi-h) (dream-request "GET" "/" {} "")))
(define
  dream-fl-multi-val
  (dream-fl-cookie-val (first (dream-resp-cookies dream-fl-multi-r1))))
(define
  dream-fl-count-h
  (fn
    (req)
    (dream-text
      (str
        (len (dream-flash-messages req))
        "/"
        (len (dream-flash-of req "info"))))))
(define
  dream-fl-multi-r2
  ((dream-flash dream-fl-count-h) (dream-request "GET" "/" {:Cookie (str "dream.flash=" dream-fl-multi-val)} "")))
(dream-fl-test
  "multi: all + filtered counts"
  (dream-resp-body dream-fl-multi-r2)
  "3/2")

(define dream-fl-tests-run! (fn () {:total (+ dream-fl-pass dream-fl-fail) :passed dream-fl-pass :failed dream-fl-fail :fails dream-fl-fails}))
