"""
tests/test_fol_formalizasyon.py — Tests for the FOL Lens (Lens #3).

Coverage:
  - TestSort:                  Sort enum, cardinality constants
  - TestVariableConstant:      Variable, Constant construction
  - TestFunctionApplication:   Function term construction
  - TestPredicate:            Predicate construction, arity
  - TestFormula:              Formula AST (Atomic through Exists)
  - TestWellFormed:           is_well_formed helper
  - TestFreeVariables:        free_variables helper
  - TestFormulaDepth:         formula_depth helper
  - TestAxiomRegistry:        All 59 defined axioms, gap register
  - TestTheoremRegistry:      15 defined theorems, dependencies
  - TestKavaidRegistry:       8 kavaid, critical flags
  - TestInferenceChains:      7 chains, hub nodes
  - TestModel:                Domain, predicate, function assignment
  - TestModelChecker:         Formula evaluation against models
  - TestExtractAxiomRefs:     Regex extraction from text
  - TestYakinlasma:           Convergence score [0, 1)
  - TestConstraintFactories:  ai_assert constraint factories
  - TestKV7Independence:      No shared mutable state with other lenses
"""

import unittest
import json


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _var(name="x", sort_val="S_Mumkin"):
    from fol_formalizasyon.types import Variable, Sort
    return Variable(name=name, sort=Sort(sort_val))


def _const(name="Hayat", sort_val="S_Sifat"):
    from fol_formalizasyon.types import Constant, Sort
    return Constant(name=name, sort=Sort(sort_val))


def _pred(name="Sudur", arg_sorts=("S_Fiil", "S_Zat")):
    from fol_formalizasyon.types import Predicate, Sort
    return Predicate(
        name=name,
        arg_sorts=tuple(Sort(s) for s in arg_sorts),
        reading=f"Test predicate {name}",
    )


# =========================================================================
# Sort enum
# =========================================================================

class TestSort(unittest.TestCase):
    """Sort enum from Appendix C.1."""

    def test_sort_count(self):
        """15 S_* sorts + BEING = 16 total."""
        from fol_formalizasyon.types import Sort
        self.assertEqual(len(Sort), 16)

    def test_sort_values(self):
        """Key sorts exist with correct string values (CamelCase per C.1)."""
        from fol_formalizasyon.types import Sort
        self.assertEqual(Sort.S_ZAT.value, "S_Zat")
        self.assertEqual(Sort.S_SIFAT.value, "S_Sifat")
        self.assertEqual(Sort.S_ISIM.value, "S_Isim")
        self.assertEqual(Sort.S_FIIL.value, "S_Fiil")
        self.assertEqual(Sort.S_ESER.value, "S_Eser")
        self.assertEqual(Sort.S_LATIFE.value, "S_Latife")
        self.assertEqual(Sort.S_MERCEK.value, "S_Mercek")
        self.assertEqual(Sort.S_MERTEBE.value, "S_Mertebe")
        self.assertEqual(Sort.BEING.value, "Being")

    def test_cardinality_constraints(self):
        """Known cardinalities from Appendix C.1."""
        from fol_formalizasyon.types import Sort, SORT_CARDINALITY
        self.assertEqual(SORT_CARDINALITY[Sort.S_ZAT], 1)
        self.assertEqual(SORT_CARDINALITY[Sort.S_SIFAT], 7)
        self.assertEqual(SORT_CARDINALITY[Sort.S_LATIFE], 7)
        self.assertEqual(SORT_CARDINALITY[Sort.S_MERCEK], 7)
        self.assertEqual(SORT_CARDINALITY[Sort.S_MERTEBE], 4)
        self.assertEqual(SORT_CARDINALITY[Sort.S_ORTAM], 3)
        self.assertEqual(SORT_CARDINALITY[Sort.S_MAKAM], 4)
        self.assertEqual(SORT_CARDINALITY[Sort.S_VACIB], 1)

    def test_unbounded_sorts(self):
        """S_FIIL and S_ESER have None (infinite) cardinality."""
        from fol_formalizasyon.types import Sort, SORT_CARDINALITY
        self.assertIsNone(SORT_CARDINALITY[Sort.S_FIIL])
        self.assertIsNone(SORT_CARDINALITY[Sort.S_ESER])


# =========================================================================
# Variable and Constant
# =========================================================================

class TestVariableConstant(unittest.TestCase):

    def test_variable_frozen(self):
        v = _var("x", "S_Mumkin")
        with self.assertRaises(AttributeError):
            v.name = "y"

    def test_variable_sort(self):
        from fol_formalizasyon.types import Sort
        v = _var("x", "S_Mumkin")
        self.assertEqual(v.sort, Sort.S_MUMKIN)

    def test_constant_frozen(self):
        c = _const("Hayat", "S_Sifat")
        with self.assertRaises(AttributeError):
            c.name = "Ilim"

    def test_constant_sort(self):
        from fol_formalizasyon.types import Sort
        c = _const("Hayat", "S_Sifat")
        self.assertEqual(c.sort, Sort.S_SIFAT)

    def test_variable_equality(self):
        v1 = _var("x", "S_Mumkin")
        v2 = _var("x", "S_Mumkin")
        self.assertEqual(v1, v2)

    def test_variable_inequality_sort(self):
        v1 = _var("x", "S_Mumkin")
        v2 = _var("x", "S_Sifat")
        self.assertNotEqual(v1, v2)


# =========================================================================
# FunctionApplication
# =========================================================================

class TestFunctionApplication(unittest.TestCase):

    def test_basic_construction(self):
        from fol_formalizasyon.types import FunctionApplication, Sort
        x = _var("x", "S_Isim")
        fa = FunctionApplication(
            func_name="sifat_of",
            args=(x,),
            result_sort=Sort.S_SIFAT,
        )
        self.assertEqual(fa.func_name, "sifat_of")
        self.assertEqual(len(fa.args), 1)
        self.assertEqual(fa.result_sort, Sort.S_SIFAT)

    def test_frozen(self):
        from fol_formalizasyon.types import FunctionApplication, Sort
        x = _var("x", "S_Isim")
        fa = FunctionApplication(
            func_name="sifat_of", args=(x,), result_sort=Sort.S_SIFAT,
        )
        with self.assertRaises(AttributeError):
            fa.func_name = "other"


# =========================================================================
# Predicate
# =========================================================================

class TestPredicate(unittest.TestCase):

    def test_predicate_arity(self):
        p = _pred("Sudur", ("S_Fiil", "S_Zat"))
        self.assertEqual(p.arity, 2)

    def test_predicate_unary(self):
        p = _pred("Mumkin", ("S_Mumkin",))
        self.assertEqual(p.arity, 1)

    def test_predicate_frozen(self):
        p = _pred("Sudur", ("S_Fiil", "S_Zat"))
        with self.assertRaises(AttributeError):
            p.name = "other"


# =========================================================================
# Formula AST
# =========================================================================

class TestFormula(unittest.TestCase):

    def _atomic(self):
        from fol_formalizasyon.types import Atomic
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        return Atomic(predicate=p, terms=(x,))

    def test_atomic_construction(self):
        a = self._atomic()
        self.assertEqual(a.predicate.name, "Mumkin")
        self.assertEqual(len(a.terms), 1)

    def test_atomic_arity_mismatch(self):
        from fol_formalizasyon.types import Atomic
        p = _pred("Sudur", ("S_Fiil", "S_Zat"))
        x = _var("x", "S_Fiil")
        with self.assertRaises(ValueError):
            Atomic(predicate=p, terms=(x,))  # arity 2, got 1 term

    def test_not(self):
        from fol_formalizasyon.types import Not
        a = self._atomic()
        n = Not(operand=a)
        self.assertIs(n.operand, a)

    def test_and(self):
        from fol_formalizasyon.types import And
        a = self._atomic()
        conj = And(left=a, right=a)
        self.assertIs(conj.left, a)
        self.assertIs(conj.right, a)

    def test_or(self):
        from fol_formalizasyon.types import Or
        a = self._atomic()
        disj = Or(left=a, right=a)
        self.assertIs(disj.left, a)

    def test_implies(self):
        from fol_formalizasyon.types import Implies
        a = self._atomic()
        imp = Implies(antecedent=a, consequent=a)
        self.assertIs(imp.antecedent, a)

    def test_forall(self):
        from fol_formalizasyon.types import ForAll
        a = self._atomic()
        x = _var("x", "S_Mumkin")
        fa = ForAll(variable=x, body=a)
        self.assertEqual(fa.variable.name, "x")
        self.assertIs(fa.body, a)

    def test_exists(self):
        from fol_formalizasyon.types import Exists
        a = self._atomic()
        x = _var("x", "S_Mumkin")
        ex = Exists(variable=x, body=a)
        self.assertEqual(ex.variable.name, "x")

    def test_nested_formula(self):
        """∀x. (Mumkin(x) → ∃y. Sudur(y, z))"""
        from fol_formalizasyon.types import (
            Atomic, Implies, ForAll, Exists,
        )
        p_mumkin = _pred("Mumkin", ("S_Mumkin",))
        p_sudur = _pred("Sudur", ("S_Fiil", "S_Zat"))
        x = _var("x", "S_Mumkin")
        y = _var("y", "S_Fiil")
        z = _var("z", "S_Zat")

        body = ForAll(
            variable=x,
            body=Implies(
                antecedent=Atomic(predicate=p_mumkin, terms=(x,)),
                consequent=Exists(
                    variable=y,
                    body=Atomic(predicate=p_sudur, terms=(y, z)),
                ),
            ),
        )
        self.assertEqual(body.variable.name, "x")


# =========================================================================
# is_well_formed
# =========================================================================

class TestWellFormed(unittest.TestCase):

    def test_matching_sorts(self):
        from fol_formalizasyon.types import Atomic, is_well_formed
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Atomic(predicate=p, terms=(x,))
        self.assertTrue(is_well_formed(f))

    def test_mismatched_sorts(self):
        from fol_formalizasyon.types import Atomic, is_well_formed
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Sifat")  # wrong sort
        f = Atomic(predicate=p, terms=(x,))
        self.assertFalse(is_well_formed(f))

    def test_being_supersort(self):
        """BEING matches any sort (universal supersort)."""
        from fol_formalizasyon.types import Atomic, is_well_formed
        p = _pred("Ordered", ("Being",))
        x = _var("x", "S_Mumkin")  # BEING accepts any
        f = Atomic(predicate=p, terms=(x,))
        self.assertTrue(is_well_formed(f))

    def test_negation_well_formed(self):
        from fol_formalizasyon.types import Atomic, Not, is_well_formed
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Not(operand=Atomic(predicate=p, terms=(x,)))
        self.assertTrue(is_well_formed(f))

    def test_quantifier_well_formed(self):
        from fol_formalizasyon.types import Atomic, ForAll, is_well_formed
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = ForAll(variable=x, body=Atomic(predicate=p, terms=(x,)))
        self.assertTrue(is_well_formed(f))


# =========================================================================
# free_variables
# =========================================================================

class TestFreeVariables(unittest.TestCase):

    def test_atomic_all_free(self):
        from fol_formalizasyon.types import Atomic, free_variables
        p = _pred("Sudur", ("S_Fiil", "S_Zat"))
        x = _var("x", "S_Fiil")
        y = _var("y", "S_Zat")
        f = Atomic(predicate=p, terms=(x, y))
        fv = free_variables(f)
        self.assertEqual(fv, {x, y})

    def test_forall_binds(self):
        from fol_formalizasyon.types import Atomic, ForAll, free_variables
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = ForAll(variable=x, body=Atomic(predicate=p, terms=(x,)))
        fv = free_variables(f)
        self.assertEqual(fv, set())

    def test_exists_binds(self):
        from fol_formalizasyon.types import Atomic, Exists, free_variables
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Exists(variable=x, body=Atomic(predicate=p, terms=(x,)))
        fv = free_variables(f)
        self.assertEqual(fv, set())

    def test_partial_binding(self):
        """∀x. Sudur(x, y) — y remains free."""
        from fol_formalizasyon.types import Atomic, ForAll, free_variables
        p = _pred("Sudur", ("S_Fiil", "S_Zat"))
        x = _var("x", "S_Fiil")
        y = _var("y", "S_Zat")
        f = ForAll(
            variable=x,
            body=Atomic(predicate=p, terms=(x, y)),
        )
        fv = free_variables(f)
        self.assertEqual(fv, {y})


# =========================================================================
# formula_depth
# =========================================================================

class TestFormulaDepth(unittest.TestCase):

    def test_atomic_depth(self):
        from fol_formalizasyon.types import Atomic, formula_depth
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Atomic(predicate=p, terms=(x,))
        self.assertEqual(formula_depth(f), 1)

    def test_negation_depth(self):
        from fol_formalizasyon.types import Atomic, Not, formula_depth
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Not(operand=Atomic(predicate=p, terms=(x,)))
        self.assertEqual(formula_depth(f), 2)

    def test_nested_depth(self):
        from fol_formalizasyon.types import Atomic, ForAll, Implies, formula_depth
        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        a = Atomic(predicate=p, terms=(x,))
        f = ForAll(variable=x, body=Implies(antecedent=a, consequent=a))
        self.assertEqual(formula_depth(f), 3)  # ForAll > Implies > Atomic


# =========================================================================
# Axiom registry
# =========================================================================

class TestAxiomRegistry(unittest.TestCase):

    def test_defined_count(self):
        """59 defined axioms (66 total minus 7 gaps)."""
        from fol_formalizasyon.axioms import defined_axiom_ids
        self.assertEqual(len(defined_axiom_ids()), 59)

    def test_gap_count(self):
        """7 gap axiom IDs: AX6–AX10, AX35, AX36."""
        from fol_formalizasyon.axioms import gap_axiom_ids
        gaps = gap_axiom_ids()
        self.assertEqual(len(gaps), 7)
        self.assertIn("AX6", gaps)
        self.assertIn("AX10", gaps)
        self.assertIn("AX35", gaps)
        self.assertIn("AX36", gaps)

    def test_get_axiom_AX1(self):
        from fol_formalizasyon.axioms import get_axiom
        ax = get_axiom("AX1")
        self.assertIsNotNone(ax)
        self.assertEqual(ax.name, "Ontological Axiom (K₁)")
        self.assertEqual(ax.layer, 1)

    def test_get_axiom_AX17(self):
        from fol_formalizasyon.axioms import get_axiom
        ax = get_axiom("AX17")
        self.assertIsNotNone(ax)
        self.assertEqual(ax.layer, 2)
        self.assertIn("esma", ax.tags)

    def test_get_axiom_gap(self):
        from fol_formalizasyon.axioms import get_axiom
        self.assertIsNone(get_axiom("AX6"))
        self.assertIsNone(get_axiom("AX35"))

    def test_get_axiom_nonexistent(self):
        from fol_formalizasyon.axioms import get_axiom
        self.assertIsNone(get_axiom("AX999"))

    def test_all_axioms_have_source_ref(self):
        """AX65: Every formalization must have a source reference."""
        from fol_formalizasyon.axioms import list_all_axioms
        for ax in list_all_axioms():
            self.assertTrue(
                len(ax.source_ref) > 0,
                f"{ax.id} has empty source_ref (AX65 violation)"
            )

    def test_axioms_by_layer(self):
        from fol_formalizasyon.axioms import axioms_by_layer
        layer1 = axioms_by_layer(1)
        self.assertTrue(len(layer1) > 0)
        for ax in layer1:
            self.assertEqual(ax.layer, 1)

    def test_axioms_by_tag(self):
        from fol_formalizasyon.axioms import axioms_by_tag
        foundational = axioms_by_tag("foundational")
        self.assertTrue(len(foundational) > 0)

    def test_list_all_axioms_sorted(self):
        """list_all_axioms returns sorted by numeric ID."""
        from fol_formalizasyon.axioms import list_all_axioms
        ids = [ax.id for ax in list_all_axioms()]
        # Extract numeric part for comparison
        nums = [int(id_[2:]) for id_ in ids]
        self.assertEqual(nums, sorted(nums))


# =========================================================================
# Theorem registry
# =========================================================================

class TestTheoremRegistry(unittest.TestCase):

    def test_defined_count(self):
        """15 defined theorems."""
        from fol_formalizasyon.axioms import list_all_theorems
        self.assertEqual(len(list_all_theorems()), 15)

    def test_gap_theorems(self):
        """T1, T2, T11 are gaps."""
        from fol_formalizasyon.axioms import gap_theorem_ids
        gaps = gap_theorem_ids()
        self.assertIn("T1", gaps)
        self.assertIn("T2", gaps)
        self.assertIn("T11", gaps)

    def test_get_theorem_T6(self):
        from fol_formalizasyon.axioms import get_theorem
        t = get_theorem("T6")
        self.assertIsNotNone(t)
        self.assertEqual(t.name, "Convergence Bound")
        self.assertIn("AX21", t.depends_on)
        self.assertIn("AX22", t.depends_on)

    def test_theorem_dependencies_exist(self):
        """All theorem dependencies should reference defined axioms."""
        from fol_formalizasyon.axioms import (
            list_all_theorems, defined_axiom_ids,
        )
        defined = defined_axiom_ids()
        for t in list_all_theorems():
            for dep in t.depends_on:
                self.assertIn(
                    dep, defined,
                    f"{t.id} depends on {dep} which is not defined"
                )

    def test_dependency_closure(self):
        """check_dependency_closure works for T6."""
        from fol_formalizasyon.axioms import check_dependency_closure
        ok, missing = check_dependency_closure(
            "T6", {"AX21", "AX22"}
        )
        self.assertTrue(ok)
        self.assertEqual(missing, [])

    def test_dependency_closure_missing(self):
        from fol_formalizasyon.axioms import check_dependency_closure
        ok, missing = check_dependency_closure("T6", {"AX21"})
        self.assertFalse(ok)
        self.assertIn("AX22", missing)


# =========================================================================
# Kavaid registry
# =========================================================================

class TestKavaidRegistry(unittest.TestCase):

    def test_count(self):
        """8 kavaid: KV1–KV8."""
        from fol_formalizasyon.axioms import list_all_kavaid
        self.assertEqual(len(list_all_kavaid()), 8)

    def test_get_kv4(self):
        from fol_formalizasyon.axioms import get_kavaid
        kv = get_kavaid("KV4")
        self.assertIsNotNone(kv)
        self.assertTrue(kv.is_critical)

    def test_get_kv7(self):
        from fol_formalizasyon.axioms import get_kavaid
        kv = get_kavaid("KV7")
        self.assertIsNotNone(kv)
        self.assertTrue(kv.is_critical)

    def test_non_critical_kavaid(self):
        from fol_formalizasyon.axioms import get_kavaid
        kv = get_kavaid("KV1")
        self.assertIsNotNone(kv)
        self.assertFalse(kv.is_critical)

    def test_all_kavaid_numbered(self):
        from fol_formalizasyon.axioms import list_all_kavaid
        ids = {kv.id for kv in list_all_kavaid()}
        expected = {f"KV{i}" for i in range(1, 9)}
        self.assertEqual(ids, expected)


# =========================================================================
# Inference chains
# =========================================================================

class TestInferenceChains(unittest.TestCase):

    def test_chain_count(self):
        from fol_formalizasyon.axioms import INFERENCE_CHAINS
        self.assertEqual(len(INFERENCE_CHAINS), 7)

    def test_chain_c1_exists(self):
        from fol_formalizasyon.axioms import INFERENCE_CHAINS
        self.assertIn("C1", INFERENCE_CHAINS)
        self.assertIn("name", INFERENCE_CHAINS["C1"])
        self.assertIn("nodes", INFERENCE_CHAINS["C1"])

    def test_all_chains_have_nodes(self):
        from fol_formalizasyon.axioms import INFERENCE_CHAINS
        for cid, chain in INFERENCE_CHAINS.items():
            self.assertTrue(
                len(chain["nodes"]) > 0,
                f"Chain {cid} has no nodes"
            )

    def test_hub_nodes(self):
        from fol_formalizasyon.axioms import HUB_NODES
        self.assertIn("KV4", HUB_NODES)
        self.assertEqual(len(HUB_NODES["KV4"]), 3)

    def test_verify_chain(self):
        from fol_formalizasyon.inference import verify_inference_chain
        ok, missing = verify_inference_chain("C1")
        self.assertTrue(ok, f"Chain C1 has undefined nodes: {missing}")

    def test_verify_all_chains(self):
        from fol_formalizasyon.inference import verify_all_chains
        results = verify_all_chains()
        for cid, (ok, missing) in results.items():
            self.assertTrue(ok, f"Chain {cid} has missing: {missing}")


# =========================================================================
# Model
# =========================================================================

class TestModel(unittest.TestCase):

    def test_set_domain(self):
        from fol_formalizasyon.inference import Model
        from fol_formalizasyon.types import Sort
        m = Model()
        m.set_domain(Sort.S_SIFAT, {"Hayat", "Ilim", "Irade"})
        self.assertEqual(len(m.get_domain(Sort.S_SIFAT)), 3)

    def test_domain_cardinality_exceeded(self):
        """Sort cardinality constraints enforced."""
        from fol_formalizasyon.inference import Model
        from fol_formalizasyon.types import Sort
        m = Model()
        with self.assertRaises(ValueError):
            # S_ZAT has cardinality 1
            m.set_domain(Sort.S_ZAT, {"a", "b"})

    def test_empty_domain(self):
        from fol_formalizasyon.inference import Model
        from fol_formalizasyon.types import Sort
        m = Model()
        self.assertEqual(m.get_domain(Sort.S_FIIL), set())

    def test_set_predicate(self):
        from fol_formalizasyon.inference import Model
        m = Model()
        m.set_predicate("test", lambda x: True)
        self.assertIsNotNone(m.get_predicate("test"))

    def test_set_constant(self):
        from fol_formalizasyon.inference import Model
        from fol_formalizasyon.types import Sort
        m = Model()
        m.set_constant("Hayat", "hayat_elem", Sort.S_SIFAT)
        val = m.get_constant("Hayat")
        self.assertEqual(val, ("hayat_elem", Sort.S_SIFAT))

    def test_summary(self):
        from fol_formalizasyon.inference import Model
        from fol_formalizasyon.types import Sort
        m = Model()
        m.set_domain(Sort.S_SIFAT, {"a", "b"})
        m.set_predicate("P", lambda x: True)
        s = m.summary()
        # Model.summary() uses Sort.value as key (CamelCase)
        self.assertIn("S_Sifat", s["domains"])
        self.assertIn("P", s["predicates"])


# =========================================================================
# ModelChecker
# =========================================================================

class TestModelChecker(unittest.TestCase):

    def _simple_model(self):
        """Model with S_MUMKIN = {a, b}, Mumkin = always True."""
        from fol_formalizasyon.inference import Model
        from fol_formalizasyon.types import Sort
        m = Model()
        m.set_domain(Sort.S_MUMKIN, {"a", "b"})
        m.set_predicate("Mumkin", lambda x: True)
        return m

    def test_atomic_true(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Atomic
        m = self._simple_model()
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Atomic(predicate=p, terms=(x,))
        self.assertTrue(checker.evaluate(f, {"x": "a"}))

    def test_atomic_false(self):
        from fol_formalizasyon.inference import Model, ModelChecker
        from fol_formalizasyon.types import Atomic, Sort
        m = Model()
        m.set_domain(Sort.S_MUMKIN, {"a"})
        m.set_predicate("Mumkin", lambda x: False)
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Atomic(predicate=p, terms=(x,))
        self.assertFalse(checker.evaluate(f, {"x": "a"}))

    def test_negation(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Atomic, Not
        m = self._simple_model()
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Not(operand=Atomic(predicate=p, terms=(x,)))
        self.assertFalse(checker.evaluate(f, {"x": "a"}))

    def test_conjunction(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Atomic, And
        m = self._simple_model()
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        a = Atomic(predicate=p, terms=(x,))
        f = And(left=a, right=a)
        self.assertTrue(checker.evaluate(f, {"x": "a"}))

    def test_disjunction(self):
        from fol_formalizasyon.inference import Model, ModelChecker
        from fol_formalizasyon.types import Atomic, Or, Sort
        m = Model()
        m.set_domain(Sort.S_MUMKIN, {"a"})
        m.set_predicate("P", lambda x: x == "a")
        m.set_predicate("Q", lambda x: False)
        checker = ModelChecker(m)

        p = _pred("P", ("S_Mumkin",))
        q = _pred("Q", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Or(
            left=Atomic(predicate=p, terms=(x,)),
            right=Atomic(predicate=q, terms=(x,)),
        )
        self.assertTrue(checker.evaluate(f, {"x": "a"}))

    def test_implication(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Atomic, Implies
        m = self._simple_model()
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        a = Atomic(predicate=p, terms=(x,))
        # T → T = T
        f = Implies(antecedent=a, consequent=a)
        self.assertTrue(checker.evaluate(f, {"x": "a"}))

    def test_forall_satisfied(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Atomic, ForAll
        m = self._simple_model()  # Mumkin always True
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = ForAll(
            variable=x,
            body=Atomic(predicate=p, terms=(x,)),
        )
        self.assertTrue(checker.evaluate(f))

    def test_forall_unsatisfied(self):
        from fol_formalizasyon.inference import Model, ModelChecker
        from fol_formalizasyon.types import Atomic, ForAll, Sort
        m = Model()
        m.set_domain(Sort.S_MUMKIN, {"a", "b"})
        m.set_predicate("Mumkin", lambda x: x == "a")
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = ForAll(
            variable=x,
            body=Atomic(predicate=p, terms=(x,)),
        )
        self.assertFalse(checker.evaluate(f))

    def test_exists_satisfied(self):
        from fol_formalizasyon.inference import Model, ModelChecker
        from fol_formalizasyon.types import Atomic, Exists, Sort
        m = Model()
        m.set_domain(Sort.S_MUMKIN, {"a", "b"})
        m.set_predicate("Mumkin", lambda x: x == "a")
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Exists(
            variable=x,
            body=Atomic(predicate=p, terms=(x,)),
        )
        self.assertTrue(checker.evaluate(f))

    def test_exists_empty_domain(self):
        """Empty domain → ∃ is False."""
        from fol_formalizasyon.inference import Model, ModelChecker
        from fol_formalizasyon.types import Atomic, Exists, Sort
        m = Model()
        # S_MUMKIN not set → empty
        m.set_predicate("Mumkin", lambda x: True)
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Exists(
            variable=x,
            body=Atomic(predicate=p, terms=(x,)),
        )
        self.assertFalse(checker.evaluate(f))

    def test_equality(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Equals
        m = self._simple_model()
        checker = ModelChecker(m)

        x = _var("x", "S_Mumkin")
        y = _var("y", "S_Mumkin")
        f = Equals(left=x, right=y)
        self.assertTrue(checker.evaluate(f, {"x": "a", "y": "a"}))
        self.assertFalse(checker.evaluate(f, {"x": "a", "y": "b"}))

    def test_constant_resolution(self):
        from fol_formalizasyon.inference import Model, ModelChecker
        from fol_formalizasyon.types import Atomic, Sort
        m = Model()
        m.set_domain(Sort.S_SIFAT, {"hayat_elem"})
        m.set_constant("Hayat", "hayat_elem", Sort.S_SIFAT)
        m.set_predicate("Active", lambda x: x == "hayat_elem")
        checker = ModelChecker(m)

        p = _pred("Active", ("S_Sifat",))
        c = _const("Hayat", "S_Sifat")
        f = Atomic(predicate=p, terms=(c,))
        self.assertTrue(checker.evaluate(f))

    def test_unbound_variable_error(self):
        from fol_formalizasyon.inference import ModelChecker
        from fol_formalizasyon.types import Atomic
        m = self._simple_model()
        checker = ModelChecker(m)

        p = _pred("Mumkin", ("S_Mumkin",))
        x = _var("x", "S_Mumkin")
        f = Atomic(predicate=p, terms=(x,))
        with self.assertRaises(ValueError):
            checker.evaluate(f, {})  # no assignment for x


# =========================================================================
# extract_axiom_refs
# =========================================================================

class TestExtractAxiomRefs(unittest.TestCase):

    def test_basic_extraction(self):
        from fol_formalizasyon.inference import extract_axiom_refs
        refs = extract_axiom_refs("Per AX17 and T6, with KV4 constraint")
        self.assertIn("AX17", refs["axioms"])
        self.assertIn("T6", refs["theorems"])
        self.assertIn("KV4", refs["kavaid"])

    def test_subscript_kv(self):
        from fol_formalizasyon.inference import extract_axiom_refs
        refs = extract_axiom_refs("KV\u2084 is critical")
        self.assertIn("KV4", refs["kavaid"])

    def test_gap_axioms_excluded(self):
        from fol_formalizasyon.inference import extract_axiom_refs
        refs = extract_axiom_refs("AX6 AX7 AX35")
        self.assertEqual(refs["axioms"], [])

    def test_multiple_axioms(self):
        from fol_formalizasyon.inference import extract_axiom_refs
        refs = extract_axiom_refs("AX1, AX17, AX37, AX64")
        self.assertEqual(refs["axioms"], ["AX1", "AX17", "AX37", "AX64"])

    def test_empty_text(self):
        from fol_formalizasyon.inference import extract_axiom_refs
        refs = extract_axiom_refs("")
        self.assertEqual(refs["axioms"], [])
        self.assertEqual(refs["theorems"], [])
        self.assertEqual(refs["kavaid"], [])

    def test_count_coverage(self):
        from fol_formalizasyon.inference import count_axiom_coverage
        found, total, ratio = count_axiom_coverage("AX1 AX2 AX3")
        self.assertEqual(found, 3)
        self.assertEqual(total, 59)
        self.assertLess(ratio, 1.0)


# =========================================================================
# yakinlasma — Convergence score
# =========================================================================

class TestYakinlasma(unittest.TestCase):

    def test_empty_text(self):
        from fol_formalizasyon.inference import yakinlasma
        score = yakinlasma("")
        self.assertEqual(score, 0.0)

    def test_some_refs(self):
        from fol_formalizasyon.inference import yakinlasma
        score = yakinlasma("AX1 AX17 T6 KV4")
        self.assertGreater(score, 0.0)
        self.assertLess(score, 1.0)

    def test_upper_bound(self):
        """Score must be < 1.0 per KV₄/T6."""
        from fol_formalizasyon.inference import yakinlasma
        # Create text referencing many axioms
        all_refs = " ".join(f"AX{i}" for i in range(1, 67))
        all_refs += " " + " ".join(f"T{i}" for i in range(1, 19))
        all_refs += " " + " ".join(f"KV{i}" for i in range(1, 9))
        score = yakinlasma(all_refs)
        self.assertLess(score, 1.0)

    def test_with_model(self):
        from fol_formalizasyon.inference import yakinlasma, Model
        from fol_formalizasyon.types import Sort
        m = Model()
        m.set_domain(Sort.S_MUMKIN, {"a"})
        score = yakinlasma("AX1 AX17", m)
        self.assertGreater(score, 0.0)
        self.assertLess(score, 1.0)

    def test_framework_summary(self):
        from fol_formalizasyon.inference import framework_summary
        s = framework_summary()
        self.assertEqual(s["axioms_defined"], 59)
        self.assertEqual(s["axioms_gap"], 7)
        self.assertEqual(s["theorems_defined"], 15)
        self.assertEqual(s["kavaid_defined"], 8)
        self.assertEqual(s["inference_chains"], 7)


# =========================================================================
# Constraint factories
# =========================================================================

class TestConstraintFactories(unittest.TestCase):

    def test_axiom_coverage_constraint(self):
        from fol_formalizasyon.constraints import axiom_coverage
        c = axiom_coverage()
        self.assertEqual(c.name, "axiom_coverage")

        data = json.dumps({"text": "AX1 AX17 AX37"})
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)
        self.assertGreater(score, 0.0)
        self.assertLess(score, 1.0)

    def test_axiom_coverage_empty(self):
        from fol_formalizasyon.constraints import axiom_coverage
        c = axiom_coverage()
        data = json.dumps({"text": ""})
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)

    def test_theorem_derivable_constraint(self):
        from fol_formalizasyon.constraints import theorem_derivable
        c = theorem_derivable()
        data = json.dumps({"text": "T6 AX21 AX22"})
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)

    def test_theorem_derivable_missing_deps(self):
        from fol_formalizasyon.constraints import theorem_derivable
        c = theorem_derivable()
        data = json.dumps({"text": "T6 AX21"})  # missing AX22
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)

    def test_kavaid_all_pass(self):
        from fol_formalizasyon.constraints import kavaid_all_pass
        c = kavaid_all_pass()
        status = {f"KV{i}": True for i in range(1, 9)}
        data = json.dumps({"kavaid_status": status})
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)

    def test_kavaid_multiplicative_gate(self):
        """AX52: One False collapses all."""
        from fol_formalizasyon.constraints import kavaid_all_pass
        c = kavaid_all_pass()
        status = {f"KV{i}": True for i in range(1, 9)}
        status["KV4"] = False  # one failure
        data = json.dumps({"kavaid_status": status})
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)
        self.assertEqual(score, 0.0)

    def test_well_formed_formulas(self):
        from fol_formalizasyon.constraints import well_formed_formulas
        c = well_formed_formulas()
        formulas = [
            {"predicate": "Mumkin", "arity": 1, "terms_count": 1},
            {"predicate": "Sudur", "arity": 2, "terms_count": 2},
        ]
        data = json.dumps({"formulas": formulas})
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)

    def test_well_formed_arity_mismatch(self):
        from fol_formalizasyon.constraints import well_formed_formulas
        c = well_formed_formulas()
        formulas = [
            {"predicate": "Sudur", "arity": 2, "terms_count": 1},
        ]
        data = json.dumps({"formulas": formulas})
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)

    def test_convergence_bound(self):
        from fol_formalizasyon.constraints import fol_convergence_bound
        c = fol_convergence_bound()
        data = json.dumps({"convergence_score": 0.75})
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)

    def test_convergence_too_high(self):
        from fol_formalizasyon.constraints import fol_convergence_bound
        c = fol_convergence_bound()
        data = json.dumps({"convergence_score": 1.0})
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)

    def test_convergence_warning(self):
        from fol_formalizasyon.constraints import fol_convergence_bound
        c = fol_convergence_bound()
        data = json.dumps({"convergence_score": 0.96})
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)

    def test_sort_grounded(self):
        from fol_formalizasyon.constraints import sort_grounded
        c = sort_grounded()
        terms = [
            {"name": "x", "sort": "S_Mumkin"},
            {"name": "y", "sort": "S_Sifat"},
        ]
        data = json.dumps({"terms": terms})
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)

    def test_sort_invalid(self):
        from fol_formalizasyon.constraints import sort_grounded
        c = sort_grounded()
        terms = [{"name": "x", "sort": "INVALID_SORT"}]
        data = json.dumps({"terms": terms})
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)

    def test_valid_fol_entry(self):
        from fol_formalizasyon.constraints import valid_fol_entry
        c = valid_fol_entry()
        data = json.dumps({
            "text": "AX1 AX17",
            "formulas": [{"arity": 1, "terms_count": 1}],
            "terms": [{"name": "x", "sort": "S_Mumkin"}],
            "convergence_score": 0.5,
        })
        ok, score, msg = c.check_fn(data)
        self.assertTrue(ok)

    def test_valid_fol_entry_bad_convergence(self):
        from fol_formalizasyon.constraints import valid_fol_entry
        c = valid_fol_entry()
        data = json.dumps({
            "text": "AX1",
            "convergence_score": 1.0,
        })
        ok, score, msg = c.check_fn(data)
        self.assertFalse(ok)


# =========================================================================
# KV₇ Independence — no shared mutable state
# =========================================================================

class TestKV7Independence(unittest.TestCase):
    """KV₇: FOL lens must have no shared state with kavram_sozlugu or mereoloji."""

    def test_no_import_of_kavram_vozlugu(self):
        """FOL modules must not import from kavram_sozlugu."""
        import fol_formalizasyon.types as t_mod
        import fol_formalizasyon.axioms as a_mod
        import fol_formalizasyon.inference as i_mod
        import fol_formalizasyon.constraints as c_mod

        for mod in [t_mod, a_mod, i_mod, c_mod]:
            src = mod.__file__
            if src:
                from pathlib import Path
                code = Path(src).read_text(encoding="utf-8")
                self.assertNotIn(
                    "from kavram_sozlugu",
                    code,
                    f"{mod.__name__} imports from kavram_sozlugu (KV₇ violation)"
                )
                self.assertNotIn(
                    "import kavram_sozlugu",
                    code,
                    f"{mod.__name__} imports kavram_sozlugu (KV₇ violation)"
                )

    def test_no_import_of_mereoloji(self):
        """FOL modules must not import from mereoloji."""
        import fol_formalizasyon.types as t_mod
        import fol_formalizasyon.axioms as a_mod
        import fol_formalizasyon.inference as i_mod
        import fol_formalizasyon.constraints as c_mod

        for mod in [t_mod, a_mod, i_mod, c_mod]:
            src = mod.__file__
            if src:
                from pathlib import Path
                code = Path(src).read_text(encoding="utf-8")
                self.assertNotIn(
                    "from mereoloji",
                    code,
                    f"{mod.__name__} imports from mereoloji (KV₇ violation)"
                )
                self.assertNotIn(
                    "import mereoloji",
                    code,
                    f"{mod.__name__} imports mereoloji (KV₇ violation)"
                )

    def test_axiom_registry_immutable(self):
        """Registry lookup returns frozen dataclasses — no mutable shared state."""
        from fol_formalizasyon.axioms import get_axiom
        ax1 = get_axiom("AX1")
        with self.assertRaises(AttributeError):
            ax1.name = "modified"


# =========================================================================
# Axiom/Theorem dataclass properties
# =========================================================================

class TestAxiomDataclass(unittest.TestCase):

    def test_axiom_frozen(self):
        from fol_formalizasyon.types import Axiom
        ax = Axiom(
            id="AXtest", name="test", formula=None,
            axiom_text="test", source_ref="test", layer=1,
        )
        with self.assertRaises(AttributeError):
            ax.name = "other"

    def test_theorem_frozen(self):
        from fol_formalizasyon.types import Theorem
        t = Theorem(
            id="Ttest", name="test", formula=None,
            statement="test", source_ref="test", layer=1,
        )
        with self.assertRaises(AttributeError):
            t.name = "other"

    def test_kavaid_frozen(self):
        from fol_formalizasyon.types import Kavaid
        kv = Kavaid(
            id="KVtest", name="test",
            constraint="test", source_ref="test",
        )
        with self.assertRaises(AttributeError):
            kv.name = "other"


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