;; lib/acl/tests/fed.sx — Phase 4: federation (peer trust, delegation,
;; cross-instance chains, revocation). fed-sx transport is mocked as a dict.

(define acl-ft-pass 0)
(define acl-ft-fail 0)
(define acl-ft-failures (list))

;; Name-based deep equality (host `=` compares symbols by unstable interned
;; identity; see lib/acl/tests/explain.sx).
(define
  acl-ft-eq?
  (fn
    (a b)
    (cond
      ((and (list? a) (list? b))
        (and (= (len a) (len b)) (acl-ft-eq-l? a b 0)))
      ((and (symbol? a) (symbol? b))
        (= (symbol->string a) (symbol->string b)))
      (else (= a b)))))
(define
  acl-ft-eq-l?
  (fn
    (a b i)
    (cond
      ((>= i (len a)) true)
      ((not (acl-ft-eq? (nth a i) (nth b i))) false)
      (else (acl-ft-eq-l? a b (+ i 1))))))

(define
  acl-ft-check!
  (fn
    (name got expected)
    (if
      (acl-ft-eq? got expected)
      (set! acl-ft-pass (+ acl-ft-pass 1))
      (do
        (set! acl-ft-fail (+ acl-ft-fail 1))
        (append!
          acl-ft-failures
          (str name "\n    expected: " expected "\n    got:      " got))))))

;; proof leaf walker (federated proofs reconstruct through the engine rule).
(define
  acl-ft-has-leaf?
  (fn
    (node target)
    (cond
      ((= node nil) false)
      ((and (dict? node) (has-key? node :via))
        (acl-ft-eq? (get node :fact) target))
      ((and (dict? node) (has-key? node :body))
        (acl-ft-any-leaf? (get node :body) target))
      (else false))))
(define
  acl-ft-any-leaf?
  (fn
    (nodes target)
    (cond
      ((= (len nodes) 0) false)
      ((acl-ft-has-leaf? (first nodes) target) true)
      (else (acl-ft-any-leaf? (rest nodes) target)))))

(define acl-ft-p? (fn (db s a r) (acl-permit? db s a r)))

;; A standard federation fixture: local trusts peer alpha at "readonly", which
;; covers read+comment. alpha delegates several capabilities to alice.
(define
  acl-ft-fixture
  (fn
    ()
    (acl-build-db
      (list
        (acl-trust (quote alpha) (quote readonly))
        (acl-level-covers (quote readonly) (quote read))
        (acl-level-covers (quote readonly) (quote comment))
        (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc))
        (acl-delegate (quote alpha) (quote alice) (quote edit) (quote doc))))))

(define
  acl-ft-run-all!
  (fn
    ()
    (do
      (let
        ((db (acl-ft-fixture)))
        (do
          (acl-ft-check!
            "trusted delegate, level covers action -> permit"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-ft-check!
            "trusted delegate, level does NOT cover action -> deny"
            (acl-ft-p? db (quote alice) (quote edit) (quote doc))
            false)
          (acl-ft-check!
            "delegated but action class uncovered (comment has no delegate)"
            (acl-ft-p? db (quote alice) (quote comment) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-level-covers (quote readonly) (quote read)) (acl-delegate (quote beta) (quote bob) (quote read) (quote doc))))))
        (acl-ft-check!
          "untrusted peer delegate -> deny"
          (acl-ft-p? db (quote bob) (quote read) (quote doc))
          false))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote readonly)) (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc))))))
        (acl-ft-check!
          "trust but no level_covers -> deny"
          (acl-ft-p? db (quote alice) (quote read) (quote doc))
          false))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote full)) (acl-level-covers (quote full) (quote read)) (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc)) (acl-delegate (quote beta) (quote bob) (quote read) (quote doc))))))
        (do
          (acl-ft-check!
            "trust is per-peer: alpha's delegate applies"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-ft-check!
            "trust not transitive: beta's delegate does not apply"
            (acl-ft-p? db (quote bob) (quote read) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote full)) (acl-level-covers (quote full) (quote read)) (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc)) (acl-deny (quote alice) (quote read) (quote doc))))))
        (acl-ft-check!
          "local deny overrides federated grant"
          (acl-ft-p? db (quote alice) (quote read) (quote doc))
          false))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote full)) (acl-level-covers (quote full) (quote read)) (acl-member-of (quote alice) (quote team)) (acl-delegate (quote alpha) (quote team) (quote read) (quote doc))))))
        (acl-ft-check!
          "federated grant to group reaches member"
          (acl-ft-p? db (quote alice) (quote read) (quote doc))
          true))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote full)) (acl-level-covers (quote full) (quote read)) (acl-child-of (quote sec) (quote book)) (acl-delegate (quote alpha) (quote u) (quote read) (quote book))))))
        (acl-ft-check!
          "federated grant on parent resource reaches child"
          (acl-ft-p? db (quote u) (quote read) (quote sec))
          true))
      (let
        ((transport {:gamma (list (acl-delegate (quote gamma) (quote carol) (quote read) (quote post))) :alpha (list (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc)))}))
        (do
          (acl-ft-check!
            "fetch known peer returns its delegates"
            (len (acl-fed-fetch transport (quote alpha)))
            1)
          (acl-ft-check!
            "fetch unknown peer returns empty"
            (len (acl-fed-fetch transport (quote delta)))
            0)
          (acl-ft-check!
            "collect across peers"
            (len
              (acl-fed-collect transport (list (quote alpha) (quote gamma))))
            2)
          (let
            ((db (acl-fed-build-db (list (acl-trust (quote alpha) (quote readonly)) (acl-trust (quote gamma) (quote readonly)) (acl-level-covers (quote readonly) (quote read))) transport (list (quote alpha) (quote gamma)))))
            (do
              (acl-ft-check!
                "fed-build-db: alpha delegate permits"
                (acl-ft-p? db (quote alice) (quote read) (quote doc))
                true)
              (acl-ft-check!
                "fed-build-db: gamma delegate permits"
                (acl-ft-p? db (quote carol) (quote read) (quote post))
                true)
              (acl-ft-check!
                "fed-build-db: untrusted action still denied"
                (acl-ft-p? db (quote alice) (quote edit) (quote doc))
                false)))))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote full)) (acl-level-covers (quote full) (quote read)) (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc))))))
        (do
          (acl-ft-check!
            "before revoke: permitted"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-revoke!
            db
            (acl-delegate
              (quote alpha)
              (quote alice)
              (quote read)
              (quote doc)))
          (acl-ft-check!
            "after delegate revoked: denied"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-trust (quote alpha) (quote full)) (acl-level-covers (quote full) (quote read)) (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc))))))
        (do
          (acl-ft-check!
            "before trust revoke: permitted"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-revoke! db (acl-trust (quote alpha) (quote full)))
          (acl-ft-check!
            "after trust revoked: denied"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-level-covers (quote full) (quote read)) (acl-delegate (quote alpha) (quote alice) (quote read) (quote doc))))))
        (do
          (acl-ft-check!
            "delegate without trust: denied"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            false)
          (acl-fed-assert! db (acl-trust (quote alpha) (quote full)))
          (acl-ft-check!
            "trust ingested then re-checked: permitted"
            (acl-ft-p? db (quote alice) (quote read) (quote doc))
            true)))
      (let
        ((db (acl-ft-fixture)))
        (let
          ((e (acl-explain db (quote alice) (quote read) (quote doc))))
          (do
            (acl-ft-check! "federated proof allowed?" (get e :allowed?) true)
            (acl-ft-check!
              "federated proof has delegate leaf"
              (acl-ft-has-leaf?
                (get e :proof)
                (list
                  (quote delegate)
                  (quote alpha)
                  (quote alice)
                  (quote read)
                  (quote doc)))
              true)
            (acl-ft-check!
              "federated proof has trust leaf"
              (acl-ft-has-leaf?
                (get e :proof)
                (list (quote trust) (quote alpha) (quote readonly)))
              true)
            (acl-ft-check!
              "federated proof has level_covers leaf"
              (acl-ft-has-leaf?
                (get e :proof)
                (list (quote level_covers) (quote readonly) (quote read)))
              true))))
      (acl-ft-check!
        "schema delegate arity valid"
        (acl-fact-valid?
          (acl-delegate (quote p) (quote s) (quote a) (quote r)))
        true)
      (acl-ft-check!
        "schema trust arity valid"
        (acl-fact-valid? (acl-trust (quote p) (quote l)))
        true)
      (acl-ft-check!
        "schema peer arity valid"
        (acl-fact-valid? (acl-peer (quote p) (quote peer)))
        true)
      (acl-ft-check!
        "schema level_covers arity valid"
        (acl-fact-valid? (acl-level-covers (quote l) (quote read)))
        true)
      (acl-ft-check!
        "schema delegate bad arity invalid"
        (acl-fact-valid? (list (quote delegate) (quote p) (quote s)))
        false))))

(define
  acl-fed-tests-run!
  (fn
    ()
    (do
      (set! acl-ft-pass 0)
      (set! acl-ft-fail 0)
      (set! acl-ft-failures (list))
      (acl-ft-run-all!)
      {:failures acl-ft-failures :total (+ acl-ft-pass acl-ft-fail) :passed acl-ft-pass :failed acl-ft-fail})))
