"""
tests/test_mereoloji.py — Tests for the Mereology Lens (Lens #2).

Coverage:
  - TestTelos:                  Telos construction and validation
  - TestPart:                   Part construction, KV₆ enforcement
  - TestWhole:                  Whole construction, AX5, AX29
  - TestPartOfEdge:             Edge construction, M1 irreflexivity
  - TestCEMAxioms:              M1–M5 via axioms module
  - TestTeleologicalAxioms:     T1–T5 via axioms module
  - TestMereologicalStructure:  Full structure: registration, queries, verification
  - TestHolographic:            AX37, KV₆, structural isomorphism
  - TestConstraintFactories:    ai_assert constraint factories
  - TestKV7Independence:        No shared mutable state with other lenses
"""

import unittest
import json


# ---------------------------------------------------------------------------
# Helpers — reusable fixtures
# ---------------------------------------------------------------------------

def _make_telos(name="test_purpose", level="local"):
    from mereoloji.types import Telos
    return Telos(name=name, level=level)


def _make_part(name="part_a", telos_name="func_a", telos_level="local",
               seed=0.5, transparent=True):
    from mereoloji.types import Part, Telos
    return Part(
        name=name,
        telos=Telos(name=telos_name, level=telos_level),
        holographic_seed=seed,
        is_transparent=transparent,
    )


def _make_whole(name="whole_x", telos_name="system_purpose",
                telos_level="global", integration="Hayat"):
    from mereoloji.types import Whole, Telos
    return Whole(
        name=name,
        telos=Telos(name=telos_name, level=telos_level),
        integration_principle=integration,
    )


def _make_valid_structure():
    """Build a minimal valid mereological structure (CEM + teleological)."""
    from mereoloji.types import Part, Whole, PartOfEdge, Telos
    from mereoloji.relations import MereologicalStructure

    s = MereologicalStructure()

    # Global whole
    whole = Whole(
        name="system",
        telos=Telos(name="coordinate", level="global"),
        integration_principle="Hayat",
    )

    # Two parts with different teloi (M4: supplementation)
    part_a = Part(
        name="module_a",
        telos=Telos(name="compute", level="local"),
        holographic_seed=0.3,
    )
    part_b = Part(
        name="module_b",
        telos=Telos(name="store", level="local"),
        holographic_seed=0.4,
    )

    # Compositional-level part for T3
    part_c = Part(
        name="subsystem_c",
        telos=Telos(name="process", level="compositional"),
        holographic_seed=0.5,
    )

    s.add_whole(whole)
    s.add_part(part_a)
    s.add_part(part_b)
    s.add_part(part_c)
    s.add_edge(PartOfEdge(part_name="module_a", whole_name="system"))
    s.add_edge(PartOfEdge(part_name="module_b", whole_name="system"))
    s.add_edge(PartOfEdge(part_name="subsystem_c", whole_name="system"))

    return s


# ===================================================================
# Test Classes
# ===================================================================


class TestTelos(unittest.TestCase):
    """Telos construction and validation."""

    def test_valid_local(self):
        t = _make_telos("func", "local")
        self.assertEqual(t.name, "func")
        self.assertEqual(t.level, "local")

    def test_valid_compositional(self):
        t = _make_telos("subsys", "compositional")
        self.assertEqual(t.level, "compositional")

    def test_valid_global(self):
        t = _make_telos("purpose", "global")
        self.assertEqual(t.level, "global")

    def test_invalid_level_raises(self):
        from mereoloji.types import Telos
        with self.assertRaises(ValueError):
            Telos(name="bad", level="cosmic")

    def test_empty_name_raises(self):
        """T1: every part must have a telos with a name."""
        from mereoloji.types import Telos
        with self.assertRaises(ValueError):
            Telos(name="", level="local")

    def test_immutability(self):
        t = _make_telos()
        with self.assertRaises(AttributeError):
            t.name = "changed"


class TestPart(unittest.TestCase):
    """Part construction and KV₆ enforcement."""

    def test_valid_construction(self):
        p = _make_part()
        self.assertEqual(p.name, "part_a")
        self.assertEqual(p.holographic_seed, 0.5)
        self.assertTrue(p.is_transparent)

    def test_kv6_zero_seed_clamped(self):
        """KV₆: holographic seed must be > 0. Zero gets clamped."""
        p = _make_part(seed=0.0)
        self.assertGreater(p.holographic_seed, 0)

    def test_kv6_negative_seed_clamped(self):
        """KV₆: negative seed gets clamped to > 0."""
        p = _make_part(seed=-5.0)
        self.assertGreater(p.holographic_seed, 0)

    def test_high_seed_allowed(self):
        p = _make_part(seed=0.99)
        self.assertEqual(p.holographic_seed, 0.99)

    def test_empty_name_raises(self):
        from mereoloji.types import Part, Telos
        with self.assertRaises(ValueError):
            Part(name="", telos=Telos(name="x", level="local"))

    def test_immutability(self):
        p = _make_part()
        with self.assertRaises(AttributeError):
            p.name = "changed"

    def test_esma_refs_read_only(self):
        """KV₇: Names references are read-only tuple — no shared mutable state."""
        p = _make_part()
        self.assertIsInstance(p.esma_refs, tuple)

    def test_ax27_transparent_default(self):
        """AX27: Parts are transparent vessels by default."""
        p = _make_part()
        self.assertTrue(p.is_transparent)


class TestWhole(unittest.TestCase):
    """Whole construction, AX5, AX29."""

    def test_valid_construction(self):
        w = _make_whole()
        self.assertEqual(w.name, "whole_x")
        self.assertEqual(w.integration_principle, "Hayat")

    def test_ax5_no_integration_raises(self):
        """AX5: Whole without integration_principle must raise."""
        from mereoloji.types import Whole, Telos
        with self.assertRaises(ValueError):
            Whole(
                name="orphan",
                telos=Telos(name="x", level="global"),
                integration_principle="",
            )

    def test_ax29_cosmic_order_multiplicative(self):
        """AX29: CosmicOrder = Nizam * İntizam (multiplicative gate)."""
        w = _make_whole()
        w.nizam_score = 0.8
        w.intizam_score = 0.6
        self.assertAlmostEqual(w.cosmic_order, 0.48, places=2)

    def test_ax29_zero_nizam_collapses(self):
        """AX29/AX52: If nizam = 0, cosmic order collapses."""
        w = _make_whole()
        w.nizam_score = 0.0
        self.assertEqual(w.cosmic_order, 0.0)

    def test_ax29_zero_intizam_collapses(self):
        """AX29/AX52: If intizam = 0, cosmic order collapses."""
        w = _make_whole()
        w.intizam_score = 0.0
        self.assertEqual(w.cosmic_order, 0.0)

    def test_score_clamped_below_one(self):
        """T6: scores clamped to [0, 1)."""
        from mereoloji.types import Whole, Telos
        w = Whole(
            name="test",
            telos=Telos(name="x", level="global"),
            integration_principle="life",
            nizam_score=5.0,
            intizam_score=5.0,
        )
        self.assertLess(w.nizam_score, 1.0)
        self.assertLess(w.intizam_score, 1.0)

    def test_to_dict(self):
        w = _make_whole()
        d = w.to_dict()
        self.assertEqual(d["name"], "whole_x")
        self.assertEqual(d["integration_principle"], "Hayat")
        self.assertIn("telos", d)

    def test_empty_name_raises(self):
        from mereoloji.types import Whole, Telos
        with self.assertRaises(ValueError):
            Whole(name="", telos=Telos(name="x", level="global"),
                  integration_principle="life")


class TestPartOfEdge(unittest.TestCase):
    """Edge construction and M1 irreflexivity."""

    def test_valid_edge(self):
        from mereoloji.types import PartOfEdge
        e = PartOfEdge(part_name="a", whole_name="b")
        self.assertEqual(e.part_name, "a")
        self.assertEqual(e.whole_name, "b")

    def test_m1_reflexive_raises(self):
        """M1: x ⊏ x must raise ValueError."""
        from mereoloji.types import PartOfEdge
        with self.assertRaises(ValueError):
            PartOfEdge(part_name="x", whole_name="x")

    def test_immutability(self):
        from mereoloji.types import PartOfEdge
        e = PartOfEdge(part_name="a", whole_name="b")
        with self.assertRaises(AttributeError):
            e.part_name = "changed"


class TestCEMAxioms(unittest.TestCase):
    """CEM M1–M5 via axioms module."""

    def test_m1_pass(self):
        from mereoloji.axioms import check_m1_irreflexivity
        from mereoloji.types import PartOfEdge
        edges = [PartOfEdge("a", "b"), PartOfEdge("c", "d")]
        ok, msgs = check_m1_irreflexivity(edges)
        self.assertTrue(ok)
        self.assertEqual(msgs, [])

    def test_m2_pass(self):
        from mereoloji.axioms import check_m2_asymmetry
        from mereoloji.types import PartOfEdge
        edges = [PartOfEdge("a", "b"), PartOfEdge("c", "d")]
        ok, msgs = check_m2_asymmetry(edges)
        self.assertTrue(ok)

    def test_m2_fail(self):
        from mereoloji.axioms import check_m2_asymmetry
        from mereoloji.types import PartOfEdge
        edges = [PartOfEdge("a", "b"), PartOfEdge("b", "a")]
        ok, msgs = check_m2_asymmetry(edges)
        self.assertFalse(ok)

    def test_m3_pass_simple(self):
        from mereoloji.axioms import check_m3_transitivity
        from mereoloji.types import PartOfEdge
        # a ⊏ b ⊏ c, and a ⊏ c exists
        edges = [
            PartOfEdge("a", "b"), PartOfEdge("b", "c"), PartOfEdge("a", "c")
        ]
        ok, msgs = check_m3_transitivity(edges, {}, {})
        self.assertTrue(ok)

    def test_m3_fail_missing(self):
        from mereoloji.axioms import check_m3_transitivity
        from mereoloji.types import PartOfEdge
        # a ⊏ b ⊏ c, but a ⊏ c is missing
        edges = [PartOfEdge("a", "b"), PartOfEdge("b", "c")]
        ok, msgs = check_m3_transitivity(edges, {}, {})
        self.assertFalse(ok)
        self.assertTrue(any("a" in m and "c" in m for m in msgs))

    def test_m4_pass(self):
        from mereoloji.axioms import check_m4_supplementation
        from mereoloji.types import PartOfEdge
        # whole "w" has 2 parts
        edges = [PartOfEdge("a", "w"), PartOfEdge("b", "w")]
        ok, msgs = check_m4_supplementation(edges)
        self.assertTrue(ok)

    def test_m4_fail_single_part(self):
        from mereoloji.axioms import check_m4_supplementation
        from mereoloji.types import PartOfEdge
        edges = [PartOfEdge("a", "w")]
        ok, msgs = check_m4_supplementation(edges)
        self.assertFalse(ok)

    def test_m5_pass(self):
        from mereoloji.axioms import check_m5_fusion
        from mereoloji.types import PartOfEdge
        parts = {"a": _make_part("a"), "b": _make_part("b")}
        wholes = {"w": _make_whole("w")}
        edges = [PartOfEdge("a", "w"), PartOfEdge("b", "w")]
        ok, msgs = check_m5_fusion(parts, wholes, edges)
        self.assertTrue(ok)

    def test_m5_fail_orphan(self):
        from mereoloji.axioms import check_m5_fusion
        from mereoloji.types import PartOfEdge
        parts = {"a": _make_part("a"), "orphan": _make_part("orphan")}
        wholes = {"w": _make_whole("w")}
        edges = [PartOfEdge("a", "w")]
        ok, msgs = check_m5_fusion(parts, wholes, edges)
        self.assertFalse(ok)

    def test_check_all_cem(self):
        s = _make_valid_structure()
        results = s.verify_cem()
        self.assertIn("M1", results)
        self.assertIn("M4", results)


class TestTeleologicalAxioms(unittest.TestCase):
    """T1–T5 via axioms module."""

    def test_t1_pass(self):
        from mereoloji.axioms import check_t1_telos_present
        parts = {"a": _make_part("a"), "b": _make_part("b")}
        ok, msgs = check_t1_telos_present(parts)
        self.assertTrue(ok)

    def test_t3_pass_all_levels(self):
        from mereoloji.axioms import check_t3_telos_ordering
        parts = {
            "a": _make_part("a", telos_level="local"),
            "b": _make_part("b", telos_name="mid", telos_level="compositional"),
        }
        wholes = {"w": _make_whole("w", telos_level="global")}
        ok, msgs = check_t3_telos_ordering(parts, wholes)
        self.assertTrue(ok)

    def test_t3_fail_missing_level(self):
        from mereoloji.axioms import check_t3_telos_ordering
        parts = {"a": _make_part("a", telos_level="local")}
        wholes = {"w": _make_whole("w", telos_level="global")}
        # Missing compositional level
        ok, msgs = check_t3_telos_ordering(parts, wholes)
        self.assertFalse(ok)

    def test_t5_pass(self):
        """Whole's telos differs from all part teloi — irreducible."""
        s = _make_valid_structure()
        results = s.verify_teleological()
        ok, msgs = results["T5"]
        self.assertTrue(ok)

    def test_t5_fail_reducible(self):
        """Whole's telos matches a part's telos — violation."""
        from mereoloji.axioms import check_t5_irreducible_telos
        from mereoloji.types import PartOfEdge
        # Part and whole have same telos name
        parts = {"a": _make_part("a", telos_name="same_purpose"),
                 "b": _make_part("b", telos_name="other")}
        wholes = {"w": _make_whole("w", telos_name="same_purpose")}
        edges = [PartOfEdge("a", "w"), PartOfEdge("b", "w")]
        ok, msgs = check_t5_irreducible_telos(wholes, parts, edges)
        self.assertFalse(ok)

    def test_check_all_teleological(self):
        s = _make_valid_structure()
        results = s.verify_teleological()
        self.assertIn("T1", results)
        self.assertIn("T5", results)


class TestMereologicalStructure(unittest.TestCase):
    """Full structure: registration, queries, verification."""

    def test_add_and_get_part(self):
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        p = _make_part("x")
        s.add_part(p)
        self.assertEqual(s.get_part("x").name, "x")

    def test_add_and_get_whole(self):
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        w = _make_whole("w")
        s.add_whole(w)
        self.assertEqual(s.get_whole("w").name, "w")

    def test_get_unknown_returns_none(self):
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        self.assertIsNone(s.get_part("nonexistent"))
        self.assertIsNone(s.get_whole("nonexistent"))

    def test_compose_convenience(self):
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        p = _make_part("p1")
        w = _make_whole("w1")
        edge = s.compose(p, w)
        self.assertEqual(edge.part_name, "p1")
        self.assertEqual(edge.whole_name, "w1")
        self.assertIsNotNone(s.get_part("p1"))
        self.assertIsNotNone(s.get_whole("w1"))

    def test_parts_of(self):
        s = _make_valid_structure()
        parts = s.parts_of("system")
        self.assertEqual(len(parts), 3)

    def test_wholes_of(self):
        s = _make_valid_structure()
        wholes = s.wholes_of("module_a")
        self.assertEqual(len(wholes), 1)
        self.assertEqual(wholes[0].name, "system")

    def test_m2_asymmetry_on_add(self):
        """M2: adding reverse edge should raise ValueError."""
        from mereoloji.relations import MereologicalStructure
        from mereoloji.types import PartOfEdge
        s = MereologicalStructure()
        s.add_edge(PartOfEdge("a", "b"))
        with self.assertRaises(ValueError):
            s.add_edge(PartOfEdge("b", "a"))

    def test_duplicate_edge_ignored(self):
        from mereoloji.relations import MereologicalStructure
        from mereoloji.types import PartOfEdge
        s = MereologicalStructure()
        s.add_edge(PartOfEdge("a", "b"))
        s.add_edge(PartOfEdge("a", "b"))  # duplicate
        self.assertEqual(len(s.all_edges()), 1)

    def test_transitive_parts(self):
        """M3: transitive closure computed correctly."""
        from mereoloji.relations import MereologicalStructure
        from mereoloji.types import PartOfEdge
        s = MereologicalStructure()
        s.add_edge(PartOfEdge("leaf", "branch"))
        s.add_edge(PartOfEdge("branch", "tree"))
        trans = s.transitive_parts("tree")
        self.assertIn("branch", trans)
        self.assertIn("leaf", trans)

    def test_verify_all_valid(self):
        s = _make_valid_structure()
        results = s.verify_all()
        # All should pass for valid structure
        for rule, (ok, msgs) in results.items():
            if rule == "M5":
                continue  # M5 may flag parts not in edges
            self.assertTrue(ok, f"{rule} failed: {msgs}")

    def test_yakinlasma_bound(self):
        """KV₄: convergence score always < 1.0."""
        s = _make_valid_structure()
        score = s.yakinlasma()
        self.assertGreater(score, 0.0)
        self.assertLess(score, 1.0)

    def test_yakinlasma_empty(self):
        """Empty structure: yakinlasma still bounded < 1.0."""
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        score = s.yakinlasma()
        self.assertLess(score, 1.0)  # KV₄ bound holds even on empty

    def test_to_dict(self):
        s = _make_valid_structure()
        d = s.to_dict()
        self.assertIn("parts", d)
        self.assertIn("wholes", d)
        self.assertIn("edges", d)
        self.assertIn("convergence_score", d)

    def test_to_json(self):
        s = _make_valid_structure()
        j = s.to_json()
        data = json.loads(j)
        self.assertIn("parts", data)

    def test_all_parts_dict(self):
        s = _make_valid_structure()
        p = s.all_parts()
        self.assertIsInstance(p, dict)
        self.assertGreater(len(p), 0)

    def test_all_wholes_dict(self):
        s = _make_valid_structure()
        w = s.all_wholes()
        self.assertIsInstance(w, dict)
        self.assertGreater(len(w), 0)


class TestHolographic(unittest.TestCase):
    """AX37, KV₆, structural isomorphism."""

    def test_holographic_seed_lookup(self):
        s = _make_valid_structure()
        seed = s.holographic_seed("module_a")
        self.assertGreater(seed, 0)

    def test_holographic_seed_unknown_raises(self):
        s = _make_valid_structure()
        with self.assertRaises(KeyError):
            s.holographic_seed("nonexistent")

    def test_kv6_pass(self):
        s = _make_valid_structure()
        ok, msgs = s.check_kv6_omnipresence()
        self.assertTrue(ok)

    def test_structural_isomorphism_same(self):
        """AX37: Same whole compared to itself → high score."""
        from mereoloji.relations import MereologicalStructure
        from mereoloji.types import Part, Whole, PartOfEdge, Telos

        s = MereologicalStructure()
        w1 = Whole(name="w1", telos=Telos(name="g1", level="global"),
                   integration_principle="life")
        w2 = Whole(name="w2", telos=Telos(name="g2", level="global"),
                   integration_principle="life")
        # Same telos structure
        p1a = Part(name="p1a", telos=Telos(name="compute", level="local"),
                   holographic_seed=0.5)
        p1b = Part(name="p1b", telos=Telos(name="store", level="local"),
                   holographic_seed=0.5)
        p2a = Part(name="p2a", telos=Telos(name="compute", level="local"),
                   holographic_seed=0.5)
        p2b = Part(name="p2b", telos=Telos(name="store", level="local"),
                   holographic_seed=0.5)

        s.add_whole(w1)
        s.add_whole(w2)
        s.compose(p1a, w1)
        s.compose(p1b, w1)
        s.compose(p2a, w2)
        s.compose(p2b, w2)

        score = s.structural_isomorphism("w1", "w2")
        self.assertGreater(score, 0.0)
        self.assertLess(score, 1.0)

    def test_structural_isomorphism_different(self):
        """Different telos sets → lower score."""
        from mereoloji.relations import MereologicalStructure
        from mereoloji.types import Part, Whole, PartOfEdge, Telos

        s = MereologicalStructure()
        w1 = Whole(name="w1", telos=Telos(name="g1", level="global"),
                   integration_principle="life")
        w2 = Whole(name="w2", telos=Telos(name="g2", level="global"),
                   integration_principle="life")
        p1 = Part(name="p1", telos=Telos(name="compute", level="local"),
                  holographic_seed=0.5)
        p2 = Part(name="p2", telos=Telos(name="paint", level="local"),
                  holographic_seed=0.5)

        s.compose(p1, w1)
        # Need a second part for w1 (M4)
        p1b = Part(name="p1b", telos=Telos(name="log", level="local"),
                   holographic_seed=0.5)
        s.compose(p1b, w1)
        s.compose(p2, w2)
        p2b = Part(name="p2b", telos=Telos(name="render", level="local"),
                   holographic_seed=0.5)
        s.compose(p2b, w2)

        score = s.structural_isomorphism("w1", "w2")
        self.assertEqual(score, 0.0)  # No shared teloi

    def test_structural_isomorphism_empty(self):
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        score = s.structural_isomorphism("a", "b")
        self.assertEqual(score, 0.0)

    def test_check_ehadiyet(self):
        """AX13: Ehadiyet check = KV₆ check."""
        s = _make_valid_structure()
        ok, msgs = s.check_ehadiyet()
        self.assertTrue(ok)

    def test_check_vahidiyet(self):
        """AX12: All parts belong to at least one whole."""
        s = _make_valid_structure()
        ok, msgs = s.check_vahidiyet()
        self.assertTrue(ok)

    def test_vahidiyet_fail_orphan(self):
        """AX12: Part not in any whole violates Vahidiyet."""
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        s.add_part(_make_part("orphan"))
        ok, msgs = s.check_vahidiyet()
        self.assertFalse(ok)


class TestSeveredCause(unittest.TestCase):
    """T10: Severed cause impossibility."""

    def test_severed_true(self):
        """Part not in any whole is severed."""
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        s.add_part(_make_part("lonely"))
        self.assertTrue(s.severed_check("lonely"))

    def test_severed_false(self):
        """Part in a whole is not severed."""
        s = _make_valid_structure()
        self.assertFalse(s.severed_check("module_a"))

    def test_integration_check_true(self):
        """AX5: Whole with integration_principle passes."""
        s = _make_valid_structure()
        self.assertTrue(s.integration_check("system"))

    def test_integration_check_false(self):
        """AX5: Unknown whole fails."""
        from mereoloji.relations import MereologicalStructure
        s = MereologicalStructure()
        self.assertFalse(s.integration_check("nonexistent"))


class TestStandaloneFunctions(unittest.TestCase):
    """is_proper_part and telos_hierarchy."""

    def test_is_proper_part_true(self):
        from mereoloji.relations import is_proper_part
        s = _make_valid_structure()
        self.assertTrue(is_proper_part("module_a", "system", s))

    def test_is_proper_part_false(self):
        from mereoloji.relations import is_proper_part
        s = _make_valid_structure()
        self.assertFalse(is_proper_part("system", "module_a", s))

    def test_is_proper_part_reflexive(self):
        """M1: Nothing is a proper part of itself."""
        from mereoloji.relations import is_proper_part
        s = _make_valid_structure()
        self.assertFalse(is_proper_part("module_a", "module_a", s))

    def test_telos_hierarchy(self):
        from mereoloji.relations import telos_hierarchy
        s = _make_valid_structure()
        h = telos_hierarchy(s)
        self.assertIn("global", h)
        self.assertIn("compositional", h)
        self.assertIn("local", h)
        self.assertGreater(len(h["local"]), 0)
        self.assertGreater(len(h["global"]), 0)


class TestConstraintFactories(unittest.TestCase):
    """ai_assert constraint factories for mereology."""

    def _make_valid_json(self):
        """A valid mereological JSON for constraint testing."""
        return json.dumps({
            "parts": {
                "p1": {
                    "telos": {"name": "compute", "level": "local"},
                    "holographic_seed": 0.5,
                },
                "p2": {
                    "telos": {"name": "store", "level": "local"},
                    "holographic_seed": 0.4,
                },
            },
            "wholes": {
                "w1": {
                    "telos": {"name": "coordinate", "level": "global"},
                    "integration_principle": "Hayat",
                    "nizam_score": 0.7,
                    "intizam_score": 0.8,
                },
            },
            "edges": [
                {"part": "p1", "whole": "w1"},
                {"part": "p2", "whole": "w1"},
            ],
            "convergence_score": 0.75,
        })

    def test_cem_valid_pass(self):
        from mereoloji.constraints import cem_valid
        c = cem_valid()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_cem_valid_m1_fail(self):
        from mereoloji.constraints import cem_valid
        c = cem_valid()
        data = json.dumps({
            "edges": [{"part": "x", "whole": "x"}]
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_cem_valid_m4_fail(self):
        from mereoloji.constraints import cem_valid
        c = cem_valid()
        data = json.dumps({
            "edges": [{"part": "a", "whole": "w"}]
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_holographic_pass(self):
        from mereoloji.constraints import holographic_omnipresence
        c = holographic_omnipresence()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_holographic_fail(self):
        from mereoloji.constraints import holographic_omnipresence
        c = holographic_omnipresence()
        data = json.dumps({
            "parts": {"p1": {"holographic_seed": 0}}
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_telos_complete_pass(self):
        from mereoloji.constraints import telos_complete
        c = telos_complete()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_telos_complete_fail(self):
        from mereoloji.constraints import telos_complete
        c = telos_complete()
        data = json.dumps({
            "parts": {"p1": {"telos": None}}
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_integration_present_pass(self):
        from mereoloji.constraints import integration_present
        c = integration_present()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_integration_present_fail(self):
        from mereoloji.constraints import integration_present
        c = integration_present()
        data = json.dumps({
            "wholes": {"w1": {"integration_principle": ""}}
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_integration_present_no_wholes(self):
        from mereoloji.constraints import integration_present
        c = integration_present()
        data = json.dumps({"wholes": {}})
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_dual_order_pass(self):
        from mereoloji.constraints import dual_order
        c = dual_order()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_dual_order_fail_zero(self):
        from mereoloji.constraints import dual_order
        c = dual_order()
        data = json.dumps({
            "wholes": {"w1": {"nizam_score": 0, "intizam_score": 0.5}}
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_convergence_bound_pass(self):
        from mereoloji.constraints import convergence_bound
        c = convergence_bound()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_convergence_bound_fail_over_one(self):
        from mereoloji.constraints import convergence_bound
        c = convergence_bound()
        data = json.dumps({"convergence_score": 1.0})
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_convergence_bound_warn_high(self):
        from mereoloji.constraints import convergence_bound
        c = convergence_bound()
        data = json.dumps({"convergence_score": 0.96})
        r = c.check(data)
        self.assertFalse(r.passed)  # >= 0.95 flagged

    def test_valid_mereological_entry_pass(self):
        from mereoloji.constraints import valid_mereological_entry
        c = valid_mereological_entry()
        r = c.check(self._make_valid_json())
        self.assertTrue(r.passed)

    def test_valid_mereological_entry_fail_no_integration(self):
        from mereoloji.constraints import valid_mereological_entry
        c = valid_mereological_entry()
        data = json.dumps({
            "parts": {"p1": {"telos": {"name": "x"}, "holographic_seed": 0.5},
                      "p2": {"telos": {"name": "y"}, "holographic_seed": 0.5}},
            "wholes": {"w1": {"integration_principle": ""}},
            "edges": [{"part": "p1", "whole": "w1"},
                      {"part": "p2", "whole": "w1"}],
        })
        r = c.check(data)
        self.assertFalse(r.passed)

    def test_all_constraint_scores_below_one(self):
        """T6: No constraint score reaches 1.0."""
        from mereoloji import constraints as mc
        factories = [
            mc.cem_valid, mc.holographic_omnipresence, mc.telos_complete,
            mc.integration_present, mc.dual_order, mc.convergence_bound,
            mc.valid_mereological_entry,
        ]
        valid_json = self._make_valid_json()
        for factory in factories:
            c = factory()
            r = c.check(valid_json)
            self.assertLess(r.score, 1.0, f"{c.name} score >= 1.0")

    def test_not_json_returns_false(self):
        from mereoloji.constraints import valid_mereological_entry
        c = valid_mereological_entry()
        r = c.check("not json at all")
        self.assertFalse(r.passed)


class TestKV7Independence(unittest.TestCase):
    """KV₇: mereoloji must not share mutable state with kavram_sozlugu."""

    def test_no_direct_kavram_sozlugu_import_in_types(self):
        """types.py should not import kavram_sozlugu."""
        import inspect
        import mereoloji.types as mt
        source = inspect.getsource(mt)
        # Check actual import statements, not docstring mentions
        for line in source.splitlines():
            stripped = line.strip()
            if stripped.startswith('#') or stripped.startswith('"""'):
                continue
            if stripped.startswith("import kavram_sozlugu") or stripped.startswith("from kavram_sozlugu"):
                self.fail(f"types.py has a real import of kavram_sozlugu: {stripped}")

    def test_no_direct_kavram_sozlugu_import_in_axioms(self):
        """axioms.py should not import kavram_sozlugu."""
        import inspect
        import mereoloji.axioms as ma
        source = inspect.getsource(ma)
        self.assertNotIn("import kavram_sozlugu", source)
        self.assertNotIn("from kavram_sozlugu", source)

    def test_no_direct_kavram_sozlugu_import_in_relations(self):
        """relations.py should not import kavram_sozlugu."""
        import inspect
        import mereoloji.relations as mr
        source = inspect.getsource(mr)
        self.assertNotIn("import kavram_sozlugu", source)
        self.assertNotIn("from kavram_sozlugu", source)

    def test_esma_refs_is_tuple(self):
        """Part.esma_refs is a tuple (immutable) — no shared mutable state."""
        p = _make_part()
        self.assertIsInstance(p.esma_refs, tuple)


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