;; lib/host/tests/relations.sx — the migrated relations read endpoints,
;; GET /internal/data/get-children and /get-parents, dispatching to lib/relations.
;; Golden tests pin each endpoint to "subsystem call + standard envelope": the
;; host adds the HTTP/JSON shell over relations/children|parents and nothing else
;; (golden derived from the same subsystem call, so result order matches).

(define host-rl-pass 0)
(define host-rl-fail 0)
(define host-rl-fails (list))

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

(define host-rl-req (fn (target) (dream-request "GET" target {} "")))
(define host-rl-app (host/make-app (list host/relations-routes)))
(define host-rl-sym (fn (s) (string->symbol s)))

;; ── seed a known graph ──────────────────────────────────────────────
;; org:1 --member--> list:7, list:8 ; org:1 --owner--> page:9
(relations/load! (list))
(relations/relate (host-rl-sym "org:1") (host-rl-sym "list:7") (host-rl-sym "member"))
(relations/relate (host-rl-sym "org:1") (host-rl-sym "list:8") (host-rl-sym "member"))
(relations/relate (host-rl-sym "org:1") (host-rl-sym "page:9") (host-rl-sym "owner"))

;; ── get-children ────────────────────────────────────────────────────
(define host-rl-kids
  "/internal/data/get-children?parent-type=org&parent-id=1&relation-type=member")
(host-rl-test "children 200" (dream-status (host-rl-app (host-rl-req host-rl-kids))) 200)
(host-rl-test
  "children has list:7"
  (contains? (dream-resp-body (host-rl-app (host-rl-req host-rl-kids))) "list:7")
  true)
(host-rl-test
  "children has list:8"
  (contains? (dream-resp-body (host-rl-app (host-rl-req host-rl-kids))) "list:8")
  true)
(host-rl-test
  "children excludes other-kind page:9"
  (contains? (dream-resp-body (host-rl-app (host-rl-req host-rl-kids))) "page:9")
  false)
(host-rl-test
  "children count via subsystem"
  (len (relations/children (host-rl-sym "org:1") (host-rl-sym "member")))
  2)

;; child-type filter narrows by node prefix.
(host-rl-test
  "children child-type=list keeps both"
  (contains?
    (dream-resp-body (host-rl-app (host-rl-req (str host-rl-kids "&child-type=list"))))
    "list:8")
  true)
(host-rl-test
  "children child-type=page filters all out"
  (contains?
    (dream-resp-body (host-rl-app (host-rl-req (str host-rl-kids "&child-type=page"))))
    "list:7")
  false)

;; ── get-parents ─────────────────────────────────────────────────────
(define host-rl-par
  "/internal/data/get-parents?child-type=list&child-id=7&relation-type=member")
(host-rl-test "parents 200" (dream-status (host-rl-app (host-rl-req host-rl-par))) 200)
(host-rl-test
  "parents has org:1"
  (contains? (dream-resp-body (host-rl-app (host-rl-req host-rl-par))) "org:1")
  true)

;; ── missing required params -> 400 ──────────────────────────────────
(host-rl-test
  "children missing param -> 400"
  (dream-status (host-rl-app (host-rl-req "/internal/data/get-children?parent-type=org")))
  400)
(host-rl-test
  "parents missing param -> 400"
  (dream-status (host-rl-app (host-rl-req "/internal/data/get-parents?child-type=list")))
  400)

;; ── golden: endpoint = subsystem call + envelope ────────────────────
(host-rl-test
  "golden children"
  (dream-resp-body (host-rl-app (host-rl-req host-rl-kids)))
  (serialize {:ok true :data (host/-rel-strings (relations/children (host-rl-sym "org:1") (host-rl-sym "member")))}))
(host-rl-test
  "golden parents"
  (dream-resp-body (host-rl-app (host-rl-req host-rl-par)))
  (serialize {:ok true :data (host/-rel-strings (relations/parents (host-rl-sym "list:7") (host-rl-sym "member")))}))

;; ── writes: attach-child / detach-child (auth + ACL + closed loop) ──
(acl/load!
  (list
    (acl-grant "carol" "relate" "relations")
    (acl-grant "carol" "unrelate" "relations")))
;; carol is permitted; dave authenticates but has no grant.
(define host-rl-resolve
  (fn (tok)
    (cond ((= tok "good") "carol") ((= tok "weak") "dave") (true nil))))
(define host-rl-wapp
  (host/make-app
    (list host/relations-routes (host/relations-write-routes host-rl-resolve))))
(define host-rl-post
  (fn (action auth body)
    (dream-request "POST" (str "/internal/actions/" action)
      (if auth {:authorization auth} {}) body)))
(define host-rl-edge
  "{:parent-type \"org\" :parent-id \"2\" :child-type \"list\" :child-id \"5\" :relation-type \"member\"}")
(define host-rl-org2
  "/internal/data/get-children?parent-type=org&parent-id=2&relation-type=member")

(relations/load! (list))

;; auth gate
(host-rl-test
  "attach no auth -> 401"
  (dream-status (host-rl-wapp (host-rl-post "attach-child" nil "{}")))
  401)
(host-rl-test
  "attach authed-but-unpermitted -> 403"
  (dream-status (host-rl-wapp (host-rl-post "attach-child" "Bearer weak" host-rl-edge)))
  403)
(host-rl-test
  "graph unchanged after 403"
  (len (relations/children (host-rl-sym "org:2") (host-rl-sym "member")))
  0)

;; permitted attach -> 201, and visible through the migrated read
(host-rl-test
  "attach authed+permitted -> 201"
  (dream-status (host-rl-wapp (host-rl-post "attach-child" "Bearer good" host-rl-edge)))
  201)
(host-rl-test
  "attached edge visible via get-children"
  (contains? (dream-resp-body (host-rl-app (host-rl-req host-rl-org2))) "list:5")
  true)

;; detach -> 200, and gone from the read
(host-rl-test
  "detach authed+permitted -> 200"
  (dream-status (host-rl-wapp (host-rl-post "detach-child" "Bearer good" host-rl-edge)))
  200)
(host-rl-test
  "detached edge gone from get-children"
  (contains? (dream-resp-body (host-rl-app (host-rl-req host-rl-org2))) "list:5")
  false)

;; bad payloads
(host-rl-test
  "attach non-object body -> 400"
  (dream-status (host-rl-wapp (host-rl-post "attach-child" "Bearer good" "(1 2)")))
  400)
(host-rl-test
  "attach missing param -> 400"
  (dream-status
    (host-rl-wapp (host-rl-post "attach-child" "Bearer good" "{:parent-type \"org\"}")))
  400)

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