;; lib/flow/tests/distributed.sx — Phase 4: distributed nodes via fed-sx (mocked).

(define flow-dist-pass 0)
(define flow-dist-fail 0)
(define flow-dist-fails (list))

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

(define flow-d (fn (src) (flow-run src)))

;; ── remote-node ─────────────────────────────────────────────────
(flow-dist-test
  "remote: a node executes on a peer"
  (flow-d
    "(flow-peer-register! (quote edge) (list (list (quote double) (lambda (x) (* x 2))))) (flow/start (remote-node (quote edge) (quote double)) 21)")
  42)
(flow-dist-test
  "remote: remote nodes compose in a sequence"
  (flow-d
    "(flow-peer-register! (quote edge) (list (list (quote inc) (lambda (x) (+ x 1))) (list (quote double) (lambda (x) (* x 2))))) (flow/start (sequence (remote-node (quote edge) (quote inc)) (remote-node (quote edge) (quote double))) 4)")
  10)
(flow-dist-test
  "remote: a remote node mixes with local nodes"
  (flow-d
    "(flow-peer-register! (quote edge) (list (list (quote double) (lambda (x) (* x 2))))) (flow/start (sequence (lambda (x) (+ x 5)) (remote-node (quote edge) (quote double)) (lambda (x) (- x 1))) 10)")
  29)
(flow-dist-test
  "remote: unreachable peer raises flow-remote-unreachable"
  (flow-d
    "(flow/start (try-catch (remote-node (quote ghost) (quote double)) (lambda (e) e)) 1)")
  "flow-remote-unreachable")
(flow-dist-test
  "remote: unknown function on a peer raises flow-remote-no-fn"
  (flow-d
    "(flow-peer-register! (quote edge) (list (list (quote double) (lambda (x) (* x 2))))) (flow/start (try-catch (remote-node (quote edge) (quote missing)) (lambda (e) e)) 1)")
  "flow-remote-no-fn")
(flow-dist-test
  "remote: a remote node can suspend the flow (peer returns control)"
  (flow-d
    "(flow-peer-register! (quote edge) (list (list (quote review) (lambda (x) x)))) (flow/start (sequence (remote-node (quote edge) (quote review)) (lambda (x) (suspend (quote human))) (lambda (v) (list (quote published) v))) 7)")
  (list "flow-suspended" 1 "human"))
(flow-dist-test
  "remote: a transient remote failure is recoverable with retry"
  (flow-d
    "(define hits 0) (flow-peer-register! (quote edge) (list (list (quote flaky) (lambda (x) (begin (set! hits (+ hits 1)) (if (< hits 2) (raise (quote down)) (* x 3))))))) (list (flow/start (retry 3 (remote-node (quote edge) (quote flaky))) 7) hits)")
  (list 21 2))

;; ── failover (retry on a different peer, fall through to local) ──
(flow-dist-test
  "failover: first reachable peer serves the request"
  (flow-d
    "(flow-peer-register! (quote p2) (list (list (quote f) (lambda (x) (+ x 100))))) (flow/start (remote-failover (list (quote p2) (quote down)) (quote f) (flow-const (quote local))) 5)")
  105)
(flow-dist-test
  "failover: skips an unreachable peer to the next one"
  (flow-d
    "(flow-peer-register! (quote p2) (list (list (quote f) (lambda (x) (+ x 100))))) (flow/start (remote-failover (list (quote down) (quote p2)) (quote f) (flow-const (quote local))) 5)")
  105)
(flow-dist-test
  "failover: skips a peer whose function raises"
  (flow-d
    "(flow-peer-register! (quote bad) (list (list (quote f) (lambda (x) (raise (quote boom)))))) (flow-peer-register! (quote good) (list (list (quote f) (lambda (x) (* x 10))))) (flow/start (remote-failover (list (quote bad) (quote good)) (quote f) (flow-const 0)) 4)")
  40)
(flow-dist-test
  "failover: all peers fail, the local fallback runs"
  (flow-d
    "(flow/start (remote-failover (list (quote down1) (quote down2)) (quote f) (lambda (x) (* x -1))) 9)")
  -9)
(flow-dist-test
  "failover: threads the input through to the chosen peer"
  (flow-d
    "(flow-peer-register! (quote p) (list (list (quote f) (lambda (x) (list (quote got) x))))) (flow/start (sequence (lambda (x) (+ x 1)) (remote-failover (list (quote p)) (quote f) (flow-const 0))) 41)")
  (list "got" 42))
(flow-dist-test
  "failover: composes inside a larger sequence"
  (flow-d
    "(flow-peer-register! (quote p) (list (list (quote f) (lambda (x) (* x 2))))) (flow/start (sequence (remote-failover (list (quote down) (quote p)) (quote f) (flow-const 1)) (lambda (x) (+ x 3))) 5)")
  13)

;; ── replication + handoff ───────────────────────────────────────
(flow-dist-test
  "replicate: a peer holds the exported store"
  (flow-d
    "(defflow w (lambda (x) (suspend (quote q)))) (flow/start w 10) (flow-replicate-to (quote peerB)) (if (flow-replica-get (quote peerB)) (quote replicated) (quote missing))")
  "replicated")
(flow-dist-test
  "handoff: a peer resumes a flow after the local instance dies"
  (flow-d
    "(defflow w (sequence (lambda (x) (suspend (quote q))) (lambda (v) (list (quote done) v)))) (define id (car (cdr (flow/start w 10)))) (flow-replicate-to (quote peerB)) (set! flow-store (list)) (flow-restore-from (quote peerB)) (flow/resume id 55)")
  (list "done" 55))
(flow-dist-test
  "handoff: restored peer reports the flow as resumable"
  (flow-d
    "(defflow w (lambda (x) (suspend (quote q)))) (define id (car (cdr (flow/start w 10)))) (flow-replicate-to (quote peerB)) (set! flow-store (list)) (flow-restore-from (quote peerB)) (flow-resumable-ids)")
  (list 1))
(flow-dist-test
  "handoff: without restore the dead instance has lost the flow"
  (flow-d
    "(defflow w (lambda (x) (suspend (quote q)))) (define id (car (cdr (flow/start w 10)))) (flow-replicate-to (quote peerB)) (set! flow-store (list)) (flow/resume id 1)")
  (list "flow-error" "no-such-flow"))
(flow-dist-test
  "restore: from an unknown peer yields false"
  (flow-d "(flow-restore-from (quote nowhere))")
  false)
(flow-dist-test
  "handoff: replication preserves the replay log across the move"
  (flow-d
    "(defflow two (sequence (lambda (x) (suspend (quote a))) (lambda (x) (suspend (quote b))) (lambda (x) (list x)))) (define id (car (cdr (flow/start two 0)))) (flow/resume id 11) (flow-replicate-to (quote peerB)) (set! flow-store (list)) (flow-restore-from (quote peerB)) (flow/resume id 22)")
  (list 22))

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