(in-package #:lispsearch/test)

;;;; M5: dependency-graph queries (json_each) and parallel enrichment.
;;;; All offline: deps/rdeps use synthetic rows; the parallel path stubs the
;;;; network fetch. (was test/m5-stretch.lisp)

(defparameter +dep-systems+
  '((:registry-name "drakma" :system-name "drakma" :description "HTTP client"
     :latest "v1" :deps "[\"cl+ssl\",\"cl-ppcre\",\"flexi-streams\"]")
    (:registry-name "dexador" :system-name "dexador" :description "HTTP client"
     :latest "v2" :deps "[\"cl+ssl\",\"quri\"]")
    (:registry-name "cl_plus_ssl" :system-name "cl+ssl" :description "TLS bindings"
     :latest "v3" :deps "[\"trivial-gray-streams\"]")
    (:registry-name "cl-ppcre" :system-name "cl-ppcre" :description "regex"
     :latest "v4" :deps "[]")))

(defun load-dep-systems (db)
  (dolist (r +dep-systems+) (ls:upsert-system db r)))

(define-test deps-queries :parent offline
  (with-fresh-db (db)
    (load-dep-systems db)
    (let ((d (ls:deps-rows db "drakma")))
      (is = 3 (length d) "drakma has 3 deps")
      (is equal '("cl+ssl" "cl-ppcre" "flexi-streams") (mapcar #'first d) "sorted by name")
      (is string= "v3" (second (find "cl+ssl" d :key #'first :test #'string=))
          "indexed dep gets latest")
      (false (second (find "flexi-streams" d :key #'first :test #'string=))
             "unindexed dep is NULL"))
    (false (ls:deps-rows db "cl-ppcre") "empty-deps system -> 0 rows")
    (is = 1 (length (ls:deps-rows db "cl_plus_ssl")) "lookup by registry_name")))

(define-test rdeps-queries :parent offline
  (with-fresh-db (db)
    (load-dep-systems db)
    (is equal '("dexador" "drakma") (mapcar #'first (ls:rdeps-rows db "cl+ssl"))
        "cl+ssl <- dexador, drakma")
    (is equal '("drakma") (mapcar #'first (ls:rdeps-rows db "cl-ppcre"))
        "cl-ppcre <- drakma only")
    (false (ls:rdeps-rows db "no-such-system") "unknown name -> empty")
    (is = 1 (length (ls:rdeps-rows db "cl+ssl" :limit 1)) "honors limit")))

(define-test parallel-enrich :parent offline
  ;; Stub the network-bound fetch so we exercise enrich-rows' orchestration
  ;; (parallel fetch + serialized DB writes) deterministically and offline.
  (let ((orig (fdefinition 'ls:fetch-enrichment))
        (calls 0)
        (lock (sb-thread:make-mutex)))
    (unwind-protect
         (progn
           (setf (fdefinition 'ls:fetch-enrichment)
                 (lambda (row)
                   (sb-thread:with-mutex (lock) (incf calls))
                   (destructuring-bind (id reg sys) row
                     (declare (ignore id))
                     (list :registry-name reg :system-name sys
                           :description (format nil "desc ~A" sys)
                           :latest "v" :deps "[]" :fetched-at (ls:iso-now)))))
           (with-fresh-db (db)
             (setf calls 0)
             (dotimes (i 25) (ls:upsert-name db (format nil "sys~D" i) (format nil "sys~D" i)))
             (is = 25 (ls:enrich-rows db (ls:pending-rows db) :jobs 8) "parallel: all 25 enriched")
             (is = 25 calls "parallel: fetch called once per row")
             (is = 25 (getf (ls:stats db) :enriched) "parallel: 25 enriched in DB"))
           (with-fresh-db (db)
             (setf calls 0)
             (dotimes (i 3) (ls:upsert-name db (format nil "x~D" i) (format nil "x~D" i)))
             (is = 3 (ls:enrich-rows db (ls:pending-rows db) :jobs 1) "sequential: all 3 enriched")
             (is = 3 calls "sequential: fetch called once per row")))
      (setf (fdefinition 'ls:fetch-enrichment) orig))))
