"""Behavior checks for the conceptual examples; Python standard library only."""
import asyncio
import importlib.util
import unittest


class ReferenceTests(unittest.TestCase):
    def require(self, name):
        self.assertIsNotNone(importlib.util.find_spec(name), f"Missing example: {name}")
        return __import__(name)

    def test_hierarchy_and_metadata_routing(self):
        memory = self.require("memory_index")
        routing = self.require("routing")
        root = memory.build_index(memory.sample_memories())
        self.assertEqual(len(list(root.walk())), 5)
        results = routing.route(root, "atlas launch", limit=10)
        self.assertIn("launch-plan", [m.id for m in results])
        self.assertNotIn("unrelated", [m.id for m in results])
        self.assertEqual(routing.route(root, "unmatchedxyz"), [])

    def test_parallelism_is_bounded_and_failure_is_visible(self):
        parallel = self.require("parallel_relevance")
        memory = self.require("memory_index")
        active = peak = 0

        async def scorer(query, item):
            nonlocal active, peak
            active += 1
            peak = max(peak, active)
            try:
                await asyncio.sleep(0.002)
                if item.id == "launch-plan":
                    raise ValueError("synthetic failure")
                return 0.5
            finally:
                active -= 1

        results = asyncio.run(parallel.rank("atlas", memory.sample_memories(),
                                           concurrency=2, scorer=scorer))
        self.assertEqual(peak, 2)
        self.assertEqual(len(results), 5)
        self.assertEqual(sum(r.error is not None for r in results), 1)
        with self.assertRaises(ValueError):
            asyncio.run(parallel.rank("atlas", [], concurrency=0))

    def test_timeout_is_not_negative_evidence(self):
        parallel = self.require("parallel_relevance")
        memory = self.require("memory_index")

        async def slow(query, item):
            await asyncio.sleep(1)
            return 1.0

        results = asyncio.run(parallel.rank("atlas", memory.sample_memories()[:1],
                                           scorer=slow, timeout=0.001))
        self.assertIsNone(results[0].score)
        self.assertIsNotNone(results[0].error)

    def test_hypothesis_changes_only_after_new_source_evidence(self):
        iteration = self.require("iterative_retrieval")
        memory = self.require("memory_index")
        trace = asyncio.run(iteration.refine(memory.sample_memories()))
        self.assertEqual(trace[0]["conclusion"], "launch planned")
        self.assertEqual(trace[-1]["conclusion"], "launch blocked by unresolved security review")
        self.assertIn("security-review", trace[-1]["evidence_ids"])
        self.assertLessEqual(len(trace), 3)
        empty = asyncio.run(iteration.refine([]))
        self.assertEqual(empty[-1]["conclusion"], "insufficient evidence")

    def test_budget_and_missing_evidence_are_explicit(self):
        iteration = self.require("iterative_retrieval")
        memory = self.require("memory_index")
        limited = asyncio.run(iteration.refine(memory.sample_memories(), max_rounds=1))
        self.assertEqual(limited[-1].get("status"), "provisional")
        self.assertEqual(limited[-1].get("stop_reason"), "round_limit")
        self.assertTrue(limited[-1].get("pending_queries"))
        incomplete = asyncio.run(iteration.refine(memory.sample_memories()[:2]))
        self.assertEqual(incomplete[-1].get("stop_reason"), "no_new_evidence")
        self.assertEqual(incomplete[-1].get("status"), "provisional")


if __name__ == "__main__":
    unittest.main()
