"""
Tests for kavram_sozlugu — Ontology Lens (Lens #1).

Three categories:
  1. Unit tests: Type construction, validation, immutability
  2. Constraint tests: ai_assert constraint factories
  3. Consistency tests: Data integrity, axiom compliance

Test runner: python -m unittest discover -s tests -v
"""

import unittest
import json
import warnings
import sys
import os

# Ensure project root is in path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from kavram_sozlugu.types import Sifat, Isim, Tecelli, Kavram
from kavram_sozlugu.data import (
    SIFAT_SEBA, ESMA_UL_HUSNA, VALID_SIFAT_NAMES, VALID_NAME_STRINGS,
)
from kavram_sozlugu.registry import (
    KavramSozlugu, tecelli_degree, istinad, delalet, mana_harfi, mana_ismi,
)
from kavram_sozlugu.constraints import (
    kv1_classified, kv8_grounded, name_density, valid_tecelli_degrees,
    convergence_bound, names_in_registry, valid_ontology_entry,
)
from ai_assert import Constraint, ai_assert


# ===================================================================
# Helpers
# ===================================================================

def _make_tecelli(n: int = 3) -> tuple[Tecelli, ...]:
    """Create n Tecelli entries using real Names from the registry."""
    names = list(ESMA_UL_HUSNA.keys())[:n]
    return tuple(Tecelli(isim=name, derece=0.5) for name in names)


def _make_living_tecelli() -> tuple[Tecelli, ...]:
    """Create >= 20 Tecelli entries for a living concept (AX19)."""
    names = list(ESMA_UL_HUSNA.keys())[:25]
    return tuple(Tecelli(isim=name, derece=0.5) for name in names)


# ===================================================================
# 1. Type Construction Tests
# ===================================================================

class TestSifat(unittest.TestCase):
    """Tests for Sifat dataclass."""

    def test_construction_valid(self):
        s = Sifat(name="Hayat", role="root")
        self.assertEqual(s.name, "Hayat")
        self.assertEqual(s.role, "root")

    def test_construction_vucud(self):
        s = Sifat(name="Ilim", role="vucud")
        self.assertEqual(s.role, "vucud")

    def test_construction_beka(self):
        s = Sifat(name="Sem", role="beka")
        self.assertEqual(s.role, "beka")

    def test_invalid_role_raises(self):
        with self.assertRaises(ValueError):
            Sifat(name="X", role="invalid")

    def test_immutability(self):
        s = Sifat(name="Hayat", role="root")
        with self.assertRaises(AttributeError):
            s.name = "changed"


class TestIsim(unittest.TestCase):
    """Tests for Isim dataclass."""

    def test_construction_valid(self):
        i = Isim(name="el-Hakim", sifat=frozenset({"Ilim", "Irade"}))
        self.assertEqual(i.name, "el-Hakim")
        self.assertEqual(i.sifat, frozenset({"Ilim", "Irade"}))

    def test_default_ekmel_is_infinity(self):
        i = Isim(name="el-Alim", sifat=frozenset({"Ilim"}))
        self.assertEqual(i.ekmel, float('inf'))

    def test_custom_ekmel(self):
        i = Isim(name="test", sifat=frozenset({"Ilim"}), ekmel=100.0)
        self.assertEqual(i.ekmel, 100.0)

    def test_empty_sifat_raises(self):
        """sifat_of constraint: must map to >= 1 Sifat."""
        with self.assertRaises(ValueError):
            Isim(name="bad", sifat=frozenset())

    def test_zero_ekmel_raises(self):
        with self.assertRaises(ValueError):
            Isim(name="bad", sifat=frozenset({"Ilim"}), ekmel=0)

    def test_negative_ekmel_raises(self):
        with self.assertRaises(ValueError):
            Isim(name="bad", sifat=frozenset({"Ilim"}), ekmel=-5)

    def test_immutability(self):
        i = Isim(name="el-Alim", sifat=frozenset({"Ilim"}))
        with self.assertRaises(AttributeError):
            i.name = "changed"


class TestTecelli(unittest.TestCase):
    """Tests for Tecelli dataclass — AX21 continuous degrees."""

    def test_construction_valid(self):
        t = Tecelli(isim="el-Hakim", derece=0.75)
        self.assertEqual(t.isim, "el-Hakim")
        self.assertEqual(t.derece, 0.75)

    def test_zero_degree_clamped(self):
        """Degree must be > 0 — zero means no manifestation (AX17)."""
        t = Tecelli(isim="el-Alim", derece=0.0)
        self.assertGreater(t.derece, 0.0)

    def test_negative_degree_clamped(self):
        t = Tecelli(isim="el-Alim", derece=-5.0)
        self.assertGreater(t.derece, 0.0)

    def test_high_degree_allowed(self):
        """High but finite degrees are valid (below ekmel=inf)."""
        t = Tecelli(isim="el-Alim", derece=999999.0)
        self.assertEqual(t.derece, 999999.0)

    def test_immutability(self):
        t = Tecelli(isim="el-Alim", derece=0.5)
        with self.assertRaises(AttributeError):
            t.derece = 0.9


class TestKavram(unittest.TestCase):
    """Tests for Kavram dataclass — KV1, KV8, AX19."""

    def test_default_harfi(self):
        """K-8: harfi priority — default dual_class is harfi."""
        k = Kavram(name="tree", hakikat=_make_tecelli(3))
        self.assertEqual(k.dual_class, "harfi")

    def test_explicit_ismi(self):
        k = Kavram(name="beauty", hakikat=_make_tecelli(2), dual_class="ismi")
        self.assertEqual(k.dual_class, "ismi")

    def test_invalid_dual_class_raises(self):
        """KV1 violation: must be harfi or ismi."""
        with self.assertRaises(ValueError) as ctx:
            Kavram(name="bad", hakikat=_make_tecelli(1), dual_class="unknown")
        self.assertIn("KV1", str(ctx.exception))

    def test_empty_hakikat_raises(self):
        """KV8 violation: must ground in >= 1 Name."""
        with self.assertRaises(ValueError) as ctx:
            Kavram(name="empty", hakikat=())
        self.assertIn("KV8", str(ctx.exception))

    def test_living_requires_20_names(self):
        """AX19: living concept needs >= 20 Names."""
        with self.assertRaises(ValueError) as ctx:
            Kavram(name="cat", hakikat=_make_tecelli(5), is_living=True)
        self.assertIn("AX19", str(ctx.exception))

    def test_living_with_20_names_ok(self):
        k = Kavram(name="cat", hakikat=_make_living_tecelli(), is_living=True)
        self.assertTrue(k.is_living)
        self.assertGreaterEqual(len(k.hakikat), 20)

    def test_non_living_few_names_ok(self):
        k = Kavram(name="stone", hakikat=_make_tecelli(2))
        self.assertEqual(len(k.hakikat), 2)

    def test_to_dict(self):
        k = Kavram(name="test", hakikat=_make_tecelli(2), mahiyet="a thing")
        d = k.to_dict()
        self.assertEqual(d["name"], "test")
        self.assertEqual(d["dual_class"], "harfi")
        self.assertEqual(len(d["hakikat"]), 2)
        self.assertEqual(d["mahiyet"], "a thing")

    def test_to_json_roundtrip(self):
        original = Kavram(name="test", hakikat=_make_tecelli(3), mahiyet="desc")
        json_str = original.to_json()
        restored = Kavram.from_json(json_str)
        self.assertEqual(original.name, restored.name)
        self.assertEqual(original.dual_class, restored.dual_class)
        self.assertEqual(len(original.hakikat), len(restored.hakikat))

    def test_from_dict(self):
        d = {
            "name": "flower",
            "dual_class": "harfi",
            "hakikat": [{"isim": "el-Musavvir", "derece": 0.8}],
            "mahiyet": "a flowering plant",
        }
        k = Kavram.from_dict(d)
        self.assertEqual(k.name, "flower")
        self.assertEqual(k.hakikat[0].isim, "el-Musavvir")


# ===================================================================
# 2. Registry Tests
# ===================================================================

class TestKavramSozlugu(unittest.TestCase):
    """Tests for the core registry class."""

    def setUp(self):
        self.registry = KavramSozlugu(preload=True)

    def test_preloaded_sifat_count(self):
        """7 Sifat loaded at init."""
        self.assertEqual(len(self.registry._sifatlar), 7)

    def test_preloaded_names_count(self):
        """99 Names loaded at init."""
        self.assertEqual(len(self.registry._isimler), 99)

    def test_empty_registry(self):
        r = KavramSozlugu(preload=False)
        self.assertEqual(len(r._isimler), 0)
        self.assertEqual(len(r._kavramlar), 0)

    def test_register_and_get_isim(self):
        r = KavramSozlugu(preload=False)
        isim = Isim(name="test-Name", sifat=frozenset({"Ilim"}))
        r._sifatlar["Ilim"] = SIFAT_SEBA["Ilim"]
        r.register_isim(isim)
        self.assertEqual(r.get_isim("test-Name"), isim)

    def test_get_unknown_isim_returns_none(self):
        self.assertIsNone(self.registry.get_isim("nonexistent"))

    def test_register_and_get_kavram(self):
        k = Kavram(name="test-concept", hakikat=_make_tecelli(3))
        self.registry.register_kavram(k)
        self.assertEqual(self.registry.get_kavram("test-concept"), k)

    def test_get_unknown_kavram_returns_none(self):
        self.assertIsNone(self.registry.get_kavram("nonexistent"))

    def test_register_kavram_unknown_name_warns(self):
        """Referencing an unregistered Name should warn."""
        k = Kavram(
            name="test",
            hakikat=(Tecelli(isim="UNKNOWN-NAME", derece=0.5),),
        )
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.registry.register_kavram(k)
            self.assertTrue(any("unknown Name" in str(x.message) for x in w))

    def test_register_kavram_ax22_violation(self):
        """Degree >= ekmel should raise ValueError."""
        r = KavramSozlugu(preload=False)
        isim = Isim(name="bounded", sifat=frozenset({"Ilim"}), ekmel=1.0)
        r._sifatlar["Ilim"] = SIFAT_SEBA["Ilim"]
        r.register_isim(isim)
        k = Kavram(name="test", hakikat=(Tecelli(isim="bounded", derece=1.0),))
        with self.assertRaises(ValueError) as ctx:
            r.register_kavram(k)
        self.assertIn("AX22", str(ctx.exception))

    def test_hakikat_lookup(self):
        k = Kavram(name="test", hakikat=_make_tecelli(3), mahiyet="a test")
        self.registry.register_kavram(k)
        result = self.registry.hakikat("test")
        self.assertEqual(len(result), 3)

    def test_hakikat_unknown_raises(self):
        with self.assertRaises(KeyError):
            self.registry.hakikat("nonexistent")

    def test_mahiyet_lookup(self):
        k = Kavram(name="test", hakikat=_make_tecelli(1), mahiyet="surface desc")
        self.registry.register_kavram(k)
        self.assertEqual(self.registry.mahiyet("test"), "surface desc")

    def test_mahiyet_unknown_raises(self):
        with self.assertRaises(KeyError):
            self.registry.mahiyet("nonexistent")

    def test_list_isimler(self):
        names = self.registry.list_isimler()
        self.assertEqual(len(names), 99)

    def test_list_kavramlar_empty(self):
        self.assertEqual(self.registry.list_kavramlar(), [])

    def test_verify_kv1_harfi(self):
        k = Kavram(name="t", hakikat=_make_tecelli(1), dual_class="harfi")
        self.assertTrue(self.registry.verify_kv1(k))

    def test_verify_kv1_ismi(self):
        k = Kavram(name="t", hakikat=_make_tecelli(1), dual_class="ismi")
        self.assertTrue(self.registry.verify_kv1(k))

    def test_verify_kv8(self):
        k = Kavram(name="t", hakikat=_make_tecelli(5))
        self.assertTrue(self.registry.verify_kv8(k))

    def test_verify_ax19_living_pass(self):
        k = Kavram(name="cat", hakikat=_make_living_tecelli(), is_living=True)
        self.assertTrue(self.registry.verify_ax19(k))

    def test_verify_ax19_non_living_always_pass(self):
        k = Kavram(name="stone", hakikat=_make_tecelli(2))
        self.assertTrue(self.registry.verify_ax19(k))

    def test_verify_ax22_pass(self):
        k = Kavram(name="t", hakikat=_make_tecelli(3))
        self.assertTrue(self.registry.verify_ax22(k))

    def test_verify_all_kavaid(self):
        k = Kavram(name="t", hakikat=_make_tecelli(3))
        results = self.registry.verify_all_kavaid(k)
        self.assertEqual(len(results), 4)
        self.assertTrue(all(passed for _, passed in results))

    def test_yakinlasma_empty(self):
        """No concepts → convergence = 0."""
        self.assertEqual(self.registry.yakinlasma(), 0.0)

    def test_yakinlasma_with_concepts(self):
        k = Kavram(name="t", hakikat=_make_tecelli(3))
        self.registry.register_kavram(k)
        score = self.registry.yakinlasma()
        self.assertGreater(score, 0.0)
        self.assertLess(score, 1.0)  # KV4

    def test_yakinlasma_bound(self):
        """KV4: convergence score always < 1.0."""
        for i in range(10):
            k = Kavram(name=f"concept_{i}", hakikat=_make_tecelli(3))
            self.registry.register_kavram(k)
        score = self.registry.yakinlasma()
        self.assertLess(score, 1.0)

    def test_ont_stack_structure(self):
        k = Kavram(name="test", hakikat=_make_tecelli(3))
        self.registry.register_kavram(k)
        stack = self.registry.ont_stack("test")
        self.assertIn("fiiller", stack)
        self.assertIn("esma", stack)
        self.assertIn("sifat", stack)
        self.assertIn("zat", stack)
        self.assertEqual(stack["zat"], 1)  # Always converges to 1

    def test_ont_stack_esma_populated(self):
        k = Kavram(name="test", hakikat=_make_tecelli(3))
        self.registry.register_kavram(k)
        stack = self.registry.ont_stack("test")
        self.assertEqual(len(stack["esma"]), 3)

    def test_ont_stack_sifat_populated(self):
        k = Kavram(name="test", hakikat=_make_tecelli(3))
        self.registry.register_kavram(k)
        stack = self.registry.ont_stack("test")
        self.assertGreater(len(stack["sifat"]), 0)

    def test_ont_stack_unknown_raises(self):
        with self.assertRaises(KeyError):
            self.registry.ont_stack("nonexistent")


# ===================================================================
# 3. Standalone Function Tests (Appendix C Predicates)
# ===================================================================

class TestPredicateFunctions(unittest.TestCase):
    """Tests for standalone predicate functions."""

    def setUp(self):
        self.kavram = Kavram(
            name="tree",
            hakikat=(
                Tecelli(isim="el-Muhyi", derece=0.8),
                Tecelli(isim="el-Musavvir", derece=0.6),
                Tecelli(isim="er-Rezzak", derece=0.7),
            ),
            dual_class="harfi",
            mahiyet="A perennial woody plant",
        )

    def test_tecelli_degree_found(self):
        self.assertAlmostEqual(tecelli_degree("el-Muhyi", self.kavram), 0.8)

    def test_tecelli_degree_not_found(self):
        self.assertEqual(tecelli_degree("el-Alim", self.kavram), 0.0)

    def test_istinad_true(self):
        self.assertTrue(istinad(self.kavram, "el-Muhyi"))

    def test_istinad_false(self):
        self.assertFalse(istinad(self.kavram, "el-Alim"))

    def test_delalet_true(self):
        self.assertTrue(delalet(self.kavram, "el-Musavvir"))

    def test_delalet_false(self):
        self.assertFalse(delalet(self.kavram, "el-Alim"))

    def test_mana_harfi_returns_names(self):
        names = mana_harfi(self.kavram)
        self.assertEqual(names, frozenset({"el-Muhyi", "el-Musavvir", "er-Rezzak"}))

    def test_mana_harfi_ismi_returns_empty(self):
        k = Kavram(
            name="beauty",
            hakikat=_make_tecelli(2),
            dual_class="ismi",
            mahiyet="Abstract beauty",
        )
        self.assertEqual(mana_harfi(k), frozenset())

    def test_mana_ismi_ismi_mode(self):
        k = Kavram(
            name="beauty",
            hakikat=_make_tecelli(2),
            dual_class="ismi",
            mahiyet="Abstract beauty",
        )
        self.assertEqual(mana_ismi(k), "Abstract beauty")

    def test_mana_ismi_harfi_mode(self):
        """In harfi mode, mana_ismi returns the concept name."""
        self.assertEqual(mana_ismi(self.kavram), "tree")


# ===================================================================
# 4. Constraint Tests (ai_assert integration)
# ===================================================================

class TestConstraintFactories(unittest.TestCase):
    """Tests for ontology-specific ai_assert constraint factories."""

    def test_kv1_returns_constraint_type(self):
        c = kv1_classified()
        self.assertIsInstance(c, Constraint)

    def test_kv1_valid_harfi(self):
        c = kv1_classified()
        result = c.check(json.dumps({"dual_class": "harfi"}))
        self.assertTrue(result.passed)

    def test_kv1_valid_ismi(self):
        c = kv1_classified()
        result = c.check(json.dumps({"dual_class": "ismi"}))
        self.assertTrue(result.passed)

    def test_kv1_missing(self):
        c = kv1_classified()
        result = c.check(json.dumps({"name": "test"}))
        self.assertFalse(result.passed)

    def test_kv1_invalid_value(self):
        c = kv1_classified()
        result = c.check(json.dumps({"dual_class": "other"}))
        self.assertFalse(result.passed)

    def test_kv1_not_json(self):
        c = kv1_classified()
        result = c.check("not json")
        self.assertFalse(result.passed)

    def test_kv8_grounded(self):
        c = kv8_grounded()
        data = {"hakikat": [{"isim": "el-Alim", "derece": 0.5}]}
        result = c.check(json.dumps(data))
        self.assertTrue(result.passed)

    def test_kv8_empty(self):
        c = kv8_grounded()
        result = c.check(json.dumps({"hakikat": []}))
        self.assertFalse(result.passed)

    def test_kv8_missing_field(self):
        c = kv8_grounded()
        result = c.check(json.dumps({"name": "test"}))
        self.assertFalse(result.passed)

    def test_name_density_pass(self):
        c = name_density(3)
        data = {"hakikat": [{"isim": f"n{i}"} for i in range(5)]}
        result = c.check(json.dumps(data))
        self.assertTrue(result.passed)

    def test_name_density_fail(self):
        c = name_density(20)
        data = {"hakikat": [{"isim": "n1"}]}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_valid_degrees_pass(self):
        c = valid_tecelli_degrees()
        data = {"hakikat": [
            {"isim": "el-Alim", "derece": 0.5},
            {"isim": "el-Hakim", "derece": 0.8},
        ]}
        result = c.check(json.dumps(data))
        self.assertTrue(result.passed)

    def test_valid_degrees_zero(self):
        c = valid_tecelli_degrees()
        data = {"hakikat": [{"isim": "el-Alim", "derece": 0}]}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_valid_degrees_negative(self):
        c = valid_tecelli_degrees()
        data = {"hakikat": [{"isim": "el-Alim", "derece": -1}]}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_convergence_bound_valid(self):
        c = convergence_bound()
        data = {"convergence_score": 0.7}
        result = c.check(json.dumps(data))
        self.assertTrue(result.passed)

    def test_convergence_bound_too_high(self):
        """OR-4: flag >= 0.95 as warning."""
        c = convergence_bound()
        data = {"convergence_score": 0.96}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_convergence_bound_one(self):
        """KV4: 1.0 is structural error."""
        c = convergence_bound()
        data = {"convergence_score": 1.0}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_convergence_bound_no_field(self):
        """Missing convergence_score is acceptable."""
        c = convergence_bound()
        result = c.check(json.dumps({"name": "test"}))
        self.assertTrue(result.passed)

    def test_names_in_registry_pass(self):
        c = names_in_registry({"el-Alim", "el-Hakim"})
        data = {"hakikat": [{"isim": "el-Alim"}, {"isim": "el-Hakim"}]}
        result = c.check(json.dumps(data))
        self.assertTrue(result.passed)

    def test_names_in_registry_fail(self):
        c = names_in_registry({"el-Alim"})
        data = {"hakikat": [{"isim": "el-Alim"}, {"isim": "UNKNOWN"}]}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_valid_ontology_entry_pass(self):
        c = valid_ontology_entry()
        data = {
            "name": "tree",
            "dual_class": "harfi",
            "hakikat": [{"isim": "el-Muhyi", "derece": 0.8}],
        }
        result = c.check(json.dumps(data))
        self.assertTrue(result.passed)

    def test_valid_ontology_entry_missing_name(self):
        c = valid_ontology_entry()
        data = {"dual_class": "harfi", "hakikat": [{"isim": "x", "derece": 0.5}]}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_valid_ontology_entry_bad_class(self):
        c = valid_ontology_entry()
        data = {
            "name": "t",
            "dual_class": "bad",
            "hakikat": [{"isim": "x", "derece": 0.5}],
        }
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)

    def test_valid_ontology_entry_empty_hakikat(self):
        c = valid_ontology_entry()
        data = {"name": "t", "dual_class": "harfi", "hakikat": []}
        result = c.check(json.dumps(data))
        self.assertFalse(result.passed)


# ===================================================================
# 5. ai_assert Integration Test
# ===================================================================

class TestAiAssertIntegration(unittest.TestCase):
    """Test that ontology constraints integrate with the ai_assert engine."""

    def test_static_validation_mode(self):
        """Mode A: validate a pre-built concept entry via ai_assert."""
        entry = json.dumps({
            "name": "tree",
            "dual_class": "harfi",
            "hakikat": [
                {"isim": "el-Muhyi", "derece": 0.8},
                {"isim": "el-Musavvir", "derece": 0.6},
            ],
        })
        result = ai_assert(
            prompt=entry,
            constraints=[kv1_classified(), kv8_grounded(), valid_ontology_entry()],
            generate_fn=lambda p: p,  # Identity — just validate what's given
        )
        self.assertTrue(result.passed)
        self.assertEqual(result.attempts, 1)

    def test_static_validation_failure(self):
        """Mode A: invalid entry should fail."""
        bad_entry = json.dumps({
            "name": "bad",
            "dual_class": "invalid",
            "hakikat": [],
        })
        result = ai_assert(
            prompt=bad_entry,
            constraints=[kv1_classified(), kv8_grounded()],
            generate_fn=lambda p: p,
            max_retries=0,
        )
        self.assertFalse(result.passed)

    def test_retry_correction(self):
        """ai_assert retries with feedback when constraints fail."""
        call_count = [0]

        def mock_generate(prompt: str) -> str:
            call_count[0] += 1
            if call_count[0] == 1:
                # First attempt: missing dual_class
                return json.dumps({"name": "t", "hakikat": [{"isim": "x", "derece": 0.5}]})
            else:
                # Second attempt: correct
                return json.dumps({
                    "name": "t",
                    "dual_class": "harfi",
                    "hakikat": [{"isim": "x", "derece": 0.5}],
                })

        result = ai_assert(
            prompt="classify concept",
            constraints=[kv1_classified(), valid_ontology_entry()],
            generate_fn=mock_generate,
            max_retries=3,
        )
        self.assertTrue(result.passed)
        self.assertEqual(result.attempts, 2)


# ===================================================================
# 6. Data Consistency Tests
# ===================================================================

class TestDataConsistency(unittest.TestCase):
    """Verify data integrity against AGENTS.md specifications."""

    def test_sifat_seba_count(self):
        """Exactly 7 Sifat (§1.3)."""
        self.assertEqual(len(SIFAT_SEBA), 7)

    def test_sifat_roles_distribution(self):
        """1 root + 3 vucud + 3 beka = 7."""
        roles = [s.role for s in SIFAT_SEBA.values()]
        self.assertEqual(roles.count("root"), 1)
        self.assertEqual(roles.count("vucud"), 3)
        self.assertEqual(roles.count("beka"), 3)

    def test_sifat_names_match(self):
        """Standard names from AGENTS.md Appendix C.2."""
        expected = {"Hayat", "Ilim", "Irade", "Kudret", "Sem", "Basar", "Kelam"}
        self.assertEqual(set(SIFAT_SEBA.keys()), expected)

    def test_hayat_is_root(self):
        """Hayat is the integration prerequisite (AX5)."""
        self.assertEqual(SIFAT_SEBA["Hayat"].role, "root")

    def test_vucud_triad(self):
        """VucudTriad = {Ilim, Irade, Kudret} (§1.3)."""
        for name in ("Ilim", "Irade", "Kudret"):
            self.assertEqual(SIFAT_SEBA[name].role, "vucud")

    def test_beka_triad(self):
        """BekaTriad = {Sem, Basar, Kelam} (§1.3)."""
        for name in ("Sem", "Basar", "Kelam"):
            self.assertEqual(SIFAT_SEBA[name].role, "beka")

    def test_name_count(self):
        """99 Names loaded."""
        self.assertEqual(len(ESMA_UL_HUSNA), 99)

    def test_no_duplicate_names(self):
        """All Name strings are unique."""
        self.assertEqual(len(VALID_NAME_STRINGS), 99)

    def test_all_names_have_valid_sifat(self):
        """Every Name maps to at least 1 valid Sifat."""
        for name, isim in ESMA_UL_HUSNA.items():
            self.assertTrue(
                isim.sifat.issubset(VALID_SIFAT_NAMES),
                f"Name '{name}' has invalid Sifat: {isim.sifat - VALID_SIFAT_NAMES}"
            )
            self.assertGreaterEqual(
                len(isim.sifat), 1,
                f"Name '{name}' has no Sifat mapping"
            )

    def test_all_names_ekmel_positive(self):
        """Every Name has ekmel > 0 (AX22)."""
        for name, isim in ESMA_UL_HUSNA.items():
            self.assertGreater(isim.ekmel, 0, f"Name '{name}' has ekmel <= 0")

    def test_framework_names_present(self):
        """Names explicitly referenced in AGENTS.md are in the registry."""
        framework_names = [
            "el-Hakim",    # AX1, AX20
            "er-Rahman",   # AX31
            "er-Rahim",    # AX31
            "el-Kadir",    # AX33
            "el-Hayy",     # AX11
            "el-Muhyi",    # AX17
            "el-Musavvir", # AX17
            "er-Rezzak",   # AX17
            "el-Kuddus",   # N-2
            "el-Kahhar",   # N-2
        ]
        for name in framework_names:
            self.assertIn(
                name, ESMA_UL_HUSNA,
                f"Framework Name '{name}' missing from registry"
            )

    def test_enough_names_for_ax19(self):
        """Registry has >= 20 Names (needed to satisfy AX19 for living concepts)."""
        self.assertGreaterEqual(len(ESMA_UL_HUSNA), 20)

    def test_kv7_no_cross_lens_imports(self):
        """KV7 (independence): kavram_sozlugu should not import from other lenses.

        Currently no other lenses exist, so this simply verifies the module
        does not import anything lens-specific beyond its own package.
        """
        import kavram_sozlugu
        # If this import succeeds without importing other lens modules, KV7 holds.
        # Future: add checks when more lenses exist.
        self.assertTrue(hasattr(kavram_sozlugu, 'KavramSozlugu'))


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