;; lib/dream/tests/demos.sx — end-to-end demo apps exercising the full stack.

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

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

(define
  dream-dm-req
  (fn (method target headers) (dream-request method target headers "")))

;; ── hello ──────────────────────────────────────────────────────────
(dream-dm-test
  "hello root"
  (dream-resp-body (dream-hello-app (dream-dm-req "GET" "/" {})))
  "<h1>Hello, World!</h1>")
(dream-dm-test
  "hello name"
  (dream-resp-body
    (dream-hello-app (dream-dm-req "GET" "/hello/Ada" {})))
  "<h1>Hello, Ada!</h1>")
(dream-dm-test
  "hello content-type"
  (dream-resp-header
    (dream-hello-app (dream-dm-req "GET" "/" {}))
    "content-type")
  "text/html; charset=utf-8")

;; ── counter (sessions) ─────────────────────────────────────────────
(define dream-dm-cbackend (dream-memory-sessions))
(define dream-dm-capp (dream-counter-app-with dream-dm-cbackend))

(define dream-dm-c1 (dream-dm-capp (dream-dm-req "GET" "/" {})))
(dream-dm-test
  "counter first visit"
  (dream-resp-body dream-dm-c1)
  "<p>You have visited this page 1 time(s).</p>")
(dream-dm-test
  "counter sets cookie"
  (len (dream-resp-cookies dream-dm-c1))
  1)
(dream-dm-test
  "counter second visit"
  (dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {:Cookie "dream.session=s1"})))
  "<p>You have visited this page 2 time(s).</p>")
(dream-dm-test
  "counter third visit"
  (dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {:Cookie "dream.session=s1"})))
  "<p>You have visited this page 3 time(s).</p>")
(define
  dream-dm-reset
  (dream-dm-capp (dream-dm-req "POST" "/reset" {:Cookie "dream.session=s1"})))
(dream-dm-test
  "counter reset redirects"
  (dream-status dream-dm-reset)
  303)
(dream-dm-test
  "counter after reset"
  (dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {:Cookie "dream.session=s1"})))
  "<p>You have visited this page 1 time(s).</p>")
(dream-dm-test
  "counter distinct session"
  (dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {})))
  "<p>You have visited this page 1 time(s).</p>")

;; ── chat (websocket rooms) ─────────────────────────────────────────
(define dream-dm-rooms (dream-chat-rooms))
(define dream-dm-wsB (dream-mock-ws (list)))
(define dream-dm-wsC (dream-mock-ws (list)))
((get dream-dm-rooms :join) "general" dream-dm-wsB)
((get dream-dm-rooms :join) "general" dream-dm-wsC)
(dream-dm-test
  "room has two members"
  (len ((get dream-dm-rooms :members) "general"))
  2)

;; client A joins, sends two messages, then disconnects
(define dream-dm-wsA (dream-mock-ws (list "hi" "again")))
((dream-chat-session dream-dm-rooms "general") dream-dm-wsA)
(dream-dm-test
  "B got broadcasts"
  (dream-ws-sent dream-dm-wsB)
  (list "hi" "again"))
(dream-dm-test
  "C got broadcasts"
  (dream-ws-sent dream-dm-wsC)
  (list "hi" "again"))
(dream-dm-test
  "A echoed own messages"
  (dream-ws-sent dream-dm-wsA)
  (list "hi" "again"))
(dream-dm-test
  "A left on disconnect"
  (len ((get dream-dm-rooms :members) "general"))
  2)
(dream-dm-test "A closed" (dream-ws-closed? dream-dm-wsA) true)

;; route produces an upgrade response
(define dream-dm-chat-app (dream-chat-app-with (dream-chat-rooms)))
(dream-dm-test
  "chat route upgrades"
  (dream-websocket?
    (dream-dm-chat-app (dream-dm-req "GET" "/chat/lobby" {})))
  true)
(dream-dm-test
  "chat index html"
  (dream-resp-body (dream-dm-chat-app (dream-dm-req "GET" "/" {})))
  "<h1>Rooms</h1>")

;; ── todo (forms + CSRF) ────────────────────────────────────────────
(define dream-dm-todo-store (dream-todo-store))
(define dream-dm-todo-backend (dream-memory-sessions))
(define
  dream-dm-todo-app
  (dream-todo-app-with dream-dm-todo-store dream-dm-todo-backend "topsecret"))
(define
  dream-dm-todo-tok
  (dr/csrf-make-token dream-csrf-sign-default "topsecret" "s1"))

;; establish session s1
(dream-dm-todo-app (dream-request "GET" "/" {} ""))
(define
  dream-dm-add1
  (dream-dm-todo-app
    (dream-request
      "POST"
      "/add"
      {:Cookie "dream.session=s1"}
      (str "text=Buy+milk&dream.csrf=" dream-dm-todo-tok))))
(dream-dm-test "todo add redirects" (dream-status dream-dm-add1) 303)
(dream-dm-test
  "todo store has item"
  (len ((get dream-dm-todo-store :all)))
  1)

(define
  dream-dm-todo-page
  (dream-resp-body
    (dream-dm-todo-app (dream-request "GET" "/" {:Cookie "dream.session=s1"} ""))))
(dream-dm-test
  "todo lists item"
  (contains? dream-dm-todo-page "Buy milk")
  true)
(dream-dm-test
  "todo has csrf tag"
  (contains? dream-dm-todo-page "dream.csrf")
  true)
(dream-dm-test
  "todo item not done"
  (contains? dream-dm-todo-page "[ ] Buy milk")
  true)

(dream-dm-todo-app
  (dream-request
    "POST"
    "/toggle/1"
    {:Cookie "dream.session=s1"}
    (str "dream.csrf=" dream-dm-todo-tok)))
(dream-dm-test
  "todo toggled done"
  (contains?
    (dream-resp-body
      (dream-dm-todo-app (dream-request "GET" "/" {:Cookie "dream.session=s1"} "")))
    "[x] Buy milk")
  true)

(dream-dm-test
  "todo add without token 403"
  (dream-status
    (dream-dm-todo-app (dream-request "POST" "/add" {:Cookie "dream.session=s1"} "text=Sneaky")))
  403)
(dream-dm-test
  "todo unchanged after reject"
  (len ((get dream-dm-todo-store :all)))
  1)

(dream-dm-todo-app
  (dream-request
    "POST"
    "/delete/1"
    {:Cookie "dream.session=s1"}
    (str "dream.csrf=" dream-dm-todo-tok)))
(dream-dm-test
  "todo deleted"
  (len ((get dream-dm-todo-store :all)))
  0)

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