(defpackage :opencortex-boot-tests
  (:use :cl :fiveam :opencortex)
  (:export #:boot-suite))
(in-package :opencortex-boot-tests)

(def-suite boot-suite :description "Verification of the Micro-Loader.")
(in-suite boot-suite)

(test test-parse-skill-metadata
  "Verify extraction of ID and DEPENDS_ON from Org headers."
  (let ((tmp-file "/tmp/org-skill-test-metadata.org"))
    (with-open-file (out tmp-file :direction :output :if-exists :supersede)
      (format out ":PROPERTIES:~%:ID: test-id~%:END:~%#+DEPENDS_ON: dep1 dep2~%"))
    (unwind-protect
         (multiple-value-bind (id deps) (opencortex::parse-skill-metadata tmp-file)
           (is (equal "test-id" id))
           (is (member "dep1" deps :test #'string=))
           (is (member "dep2" deps :test #'string=)))
      (uiop:delete-file-if-exists tmp-file))))

(test test-topological-sort-basic
  "Verify that skills are ordered by dependency."
  (let ((tmp-dir "/tmp/opencortex-boot-test/"))
    (uiop:ensure-all-directories-exist (list tmp-dir))
    ;; A depends on B
    (with-open-file (out (merge-pathnames "org-skill-a.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out "#+DEPENDS_ON: id:skill-b-id~%"))
    (with-open-file (out (merge-pathnames "org-skill-b.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out ":PROPERTIES:~%:ID: skill-b-id~%:END:~%"))
    ;; Add executive soul (required)
    (with-open-file (out (merge-pathnames "org-skill-agent.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out "#+TITLE: Agent~%"))
    
    (unwind-protect
         (let ((sorted (opencortex::topological-sort-skills tmp-dir)))
           ;; B must appear before A
           (let ((pos-a (position "org-skill-a" sorted :key #'pathname-name :test #'string-equal))
                 (pos-b (position "org-skill-b" sorted :key #'pathname-name :test #'string-equal)))
             (is (not (null pos-a)))
             (is (not (null pos-b)))
             (is (< pos-b pos-a))))
      (uiop:delete-directory-tree (uiop:ensure-directory-pathname tmp-dir) :validate t))))

(test test-topological-sort-circular
  "Verify that circular dependencies raise an error."
  (let ((tmp-dir "/tmp/opencortex-boot-test-circ/"))
    (uiop:ensure-all-directories-exist (list tmp-dir))
    ;; A depends on B, B depends on A
    (with-open-file (out (merge-pathnames "org-skill-a.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out "#+DEPENDS_ON: org-skill-b~%"))
    (with-open-file (out (merge-pathnames "org-skill-b.org" tmp-dir) :direction :output :if-exists :supersede)
      (format out "#+DEPENDS_ON: org-skill-a~%"))
    
    (unwind-protect
         (signals error (opencortex::topological-sort-skills tmp-dir))
      (uiop:delete-directory-tree (uiop:ensure-directory-pathname tmp-dir) :validate t))))

(test test-skill-jailing
  "Verify that skills are loaded into their own packages."
  (let ((tmp-skill "/tmp/org-skill-jail-test.org"))
    (with-open-file (out tmp-skill :direction :output :if-exists :supersede)
      (format out "#+begin_src lisp~%(defvar *jailed-var* 42)~%#+end_src"))
    (unwind-protect
         (progn
           (opencortex::load-skill-from-org tmp-skill)
           (let ((pkg (find-package :OPENCORTEX.SKILLS.ORG-SKILL-JAIL-TEST)))
             (is (not (null pkg)))
             (is (= 42 (symbol-value (find-symbol "*JAILED-VAR*" pkg))))))
      (uiop:delete-file-if-exists tmp-skill))))

(test test-syntax-validation
  "Verify that malformed Lisp is caught by the pre-flight check."
  (is (nth-value 0 (opencortex::validate-lisp-syntax "(defun x () t)")))
  (is (not (nth-value 0 (opencortex::validate-lisp-syntax "(defun x (")))))
