;; lib/prolog/tests/integration.sx — end-to-end integration tests via pl-query-* API
;;
;; Tests the full source→parse→load→solve pipeline with real programs.
;; Covers: permission system, graph reachability, quicksort, fibonacci, dynamic KB.

(define pl-int-test-count 0)
(define pl-int-test-pass 0)
(define pl-int-test-fail 0)
(define pl-int-test-failures (list))

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

;; ── Permission system ──
;; role/2 + permission/2 facts, allowed/2 rule

(define
  pl-int-perm-src
  "role(alice, admin). role(bob, editor). role(charlie, viewer). permission(admin, read). permission(admin, write). permission(admin, delete). permission(editor, read). permission(editor, write). permission(viewer, read). allowed(U, A) :- role(U, R), permission(R, A).")

(define pl-int-perm-db (pl-load pl-int-perm-src))

(pl-int-test!
  "alice can read"
  (len (pl-query-all pl-int-perm-db "allowed(alice, read)"))
  1)

(pl-int-test!
  "alice can delete"
  (len (pl-query-all pl-int-perm-db "allowed(alice, delete)"))
  1)

(pl-int-test!
  "charlie cannot write"
  (len (pl-query-all pl-int-perm-db "allowed(charlie, write)"))
  0)

(pl-int-test!
  "alice has 3 permissions"
  (len (pl-query-all pl-int-perm-db "allowed(alice, A)"))
  3)

(pl-int-test!
  "only one user can delete"
  (len (pl-query-all pl-int-perm-db "allowed(U, delete)"))
  1)

(pl-int-test!
  "the deleter is alice"
  (dict-get (first (pl-query-all pl-int-perm-db "allowed(U, delete)")) "U")
  "alice")

;; ── Graph reachability ──
;; Directed edges; path/2 transitive closure via two clauses

(define
  pl-int-graph-src
  "edge(a, b). edge(b, c). edge(c, d). edge(b, d). path(X, Y) :- edge(X, Y). path(X, Y) :- edge(X, Z), path(Z, Y).")

(define pl-int-graph-db (pl-load pl-int-graph-src))

(pl-int-test!
  "direct edge a→b is a path"
  (len (pl-query-all pl-int-graph-db "path(a, b)"))
  1)

(pl-int-test!
  "transitive path a→c"
  (len (pl-query-all pl-int-graph-db "path(a, c)"))
  1)

(pl-int-test!
  "no path d→a (no back-edges)"
  (len (pl-query-all pl-int-graph-db "path(d, a)"))
  0)

(pl-int-test!
  "4 derivations from a (b,c,d via two routes to d)"
  (len (pl-query-all pl-int-graph-db "path(a, Y)"))
  4)

;; ── Quicksort ──
;; Partition-and-recurse; uses its own append/3 to avoid DB pollution

(define
  pl-int-qs-src
  "partition(_, [], [], []). partition(Piv, [H|T], [H|Less], Greater) :- H =< Piv, !, partition(Piv, T, Less, Greater). partition(Piv, [H|T], Less, [H|Greater]) :- partition(Piv, T, Less, Greater). append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R). quicksort([], []). quicksort([H|T], Sorted) :- partition(H, T, Less, Greater), quicksort(Less, SL), quicksort(Greater, SG), append(SL, [H|SG], Sorted).")

(define pl-int-qs-db (pl-load pl-int-qs-src))

(pl-int-test!
  "quicksort([]) = [] (ground check)"
  (len (pl-query-all pl-int-qs-db "quicksort([], [])"))
  1)

(pl-int-test!
  "quicksort([3,1,2]) = [1,2,3] (ground check)"
  (len (pl-query-all pl-int-qs-db "quicksort([3,1,2], [1,2,3])"))
  1)

(pl-int-test!
  "quicksort([5,3,1,4,2]) = [1,2,3,4,5] (ground check)"
  (len (pl-query-all pl-int-qs-db "quicksort([5,3,1,4,2], [1,2,3,4,5])"))
  1)

(pl-int-test!
  "quicksort([3,1,2], [3,1,2]) fails — unsorted order rejected"
  (len (pl-query-all pl-int-qs-db "quicksort([3,1,2], [3,1,2])"))
  0)

;; ── Fibonacci ──
;; Naive recursive; ground checks avoid list-format uncertainty

(define
  pl-int-fib-src
  "fib(0, 0). fib(1, 1). fib(N, F) :- N > 1, N1 is N - 1, N2 is N - 2, fib(N1, F1), fib(N2, F2), F is F1 + F2.")

(define pl-int-fib-db (pl-load pl-int-fib-src))

(pl-int-test!
  "fib(0, 0) succeeds"
  (len (pl-query-all pl-int-fib-db "fib(0, 0)"))
  1)

(pl-int-test!
  "fib(5, 5) succeeds"
  (len (pl-query-all pl-int-fib-db "fib(5, 5)"))
  1)

(pl-int-test!
  "fib(7, 13) succeeds"
  (len (pl-query-all pl-int-fib-db "fib(7, 13)"))
  1)

;; ── Dynamic knowledge base ──
;; Assert and retract facts; the DB dict is mutable so mutations persist

(define pl-int-dyn-src "color(red). color(green). color(blue).")
(define pl-int-dyn-db (pl-load pl-int-dyn-src))

(pl-int-test!
  "initial KB: 3 colors"
  (len (pl-query-all pl-int-dyn-db "color(X)"))
  3)

(pl-int-test!
  "after assert(color(yellow)): 4 colors"
  (begin
    (pl-query-all pl-int-dyn-db "assert(color(yellow))")
    (len (pl-query-all pl-int-dyn-db "color(X)")))
  4)

(pl-int-test!
  "after retract(color(red)): back to 3 colors"
  (begin
    (pl-query-all pl-int-dyn-db "retract(color(red))")
    (len (pl-query-all pl-int-dyn-db "color(X)")))
  3)

(define pl-integration-tests-run! (fn () {:failed pl-int-test-fail :passed pl-int-test-pass :total pl-int-test-count :failures pl-int-test-failures}))
