"""
tests/test_emergence_harmony.py — Cross-module harmony verification.

Empirically tests whether the emergence module integrates correctly with
the rest of the dusun system by checking:

  1. CONTRACT COMPLIANCE:  Same interface as all other modules
     (verify_all → Dict[str,Tuple[bool,str]], yakinlasma → float,
      framework_summary → dict with expected keys)

  2. STRUCTURAL BOUNDS:    KV₄, T17, AX52, AX56 — same invariants
     that every other module must satisfy

  3. fw_doctor VISIBILITY: Module appears in system health checks

  4. KV₇ INDEPENDENCE:     No cross-module imports (stdlib only)

  5. OUTPUT SHAPE PARITY:  framework_summary() keys overlap with
     the canonical shape used by all other modules

Run:  python -m unittest tests.test_emergence_harmony -v
"""

import importlib
import inspect
import re
import unittest
from typing import Any, Dict, List, Tuple


# ── Reference modules that define the "contract" ─────────────────────

# Each entry: (module_path, label, has_verify_all, has_yakinlasma, has_framework_summary)
REFERENCE_MODULES = [
    ("kavram_sozlugu.constraints", "Ontoloji", True, True, True),
    ("mereoloji.constraints", "Mereoloji", True, True, True),
    ("fol_formalizasyon.inference", "FOL", True, True, True),
    ("bayes_analiz.verification", "Bayes", True, True, True),
    ("oyun_teorisi.verification", "OyunTeorisi", True, False, True),
    ("kategori_teorisi.verification", "KategoriTeorisi", True, True, True),
    ("holografik.verification", "Holografik", True, True, True),
    ("bileshke.quality", "Bileshke", False, False, True),
    ("kaskad.verification", "Kaskad", True, True, True),
]

TARGET_MODULE = "emergence.emergence"
TARGET_LABEL = "Emergence"


# =====================================================================
#  1. Contract Compliance
# =====================================================================

class TestContractCompliance(unittest.TestCase):
    """Verify emergence exposes the same interface as other modules."""

    @classmethod
    def setUpClass(cls):
        cls.mod = importlib.import_module(TARGET_MODULE)

    def test_has_verify_all(self):
        fn = getattr(self.mod, "verify_all", None)
        self.assertIsNotNone(fn, "emergence must expose verify_all()")
        self.assertTrue(callable(fn))

    def test_verify_all_returns_dict_of_bool_str(self):
        result = self.mod.verify_all()
        self.assertIsInstance(result, dict, "verify_all must return dict")
        for key, val in result.items():
            self.assertIsInstance(key, str, f"key {key!r} must be str")
            self.assertIsInstance(val, tuple, f"value for {key!r} must be tuple")
            self.assertEqual(len(val), 2, f"tuple for {key!r} must have 2 elements")
            self.assertIsInstance(val[0], bool, f"first element for {key!r} must be bool")
            self.assertIsInstance(val[1], str, f"second element for {key!r} must be str")

    def test_verify_all_nonempty(self):
        result = self.mod.verify_all()
        self.assertGreater(len(result), 0, "verify_all must return at least 1 check")

    def test_has_yakinlasma(self):
        fn = getattr(self.mod, "yakinlasma", None)
        self.assertIsNotNone(fn, "emergence must expose yakinlasma()")
        self.assertTrue(callable(fn))

    def test_yakinlasma_returns_float(self):
        score = self.mod.yakinlasma()
        self.assertIsInstance(score, float, "yakinlasma must return float")

    def test_yakinlasma_in_valid_range(self):
        """KV₄: 0 < score < 1 (never 0.0, never ≥ 1.0)."""
        score = self.mod.yakinlasma()
        self.assertGreater(score, 0.0, "yakinlasma must be > 0 (non-trivial)")
        self.assertLess(score, 1.0, "yakinlasma must be < 1.0 (KV₄)")

    def test_has_framework_summary(self):
        fn = getattr(self.mod, "framework_summary", None)
        self.assertIsNotNone(fn, "emergence must expose framework_summary()")
        self.assertTrue(callable(fn))

    def test_framework_summary_returns_dict(self):
        summary = self.mod.framework_summary()
        self.assertIsInstance(summary, dict)
        self.assertGreater(len(summary), 0)


# =====================================================================
#  2. Structural Bounds (KV₄, T17, AX52, AX56)
# =====================================================================

class TestStructuralBounds(unittest.TestCase):
    """Verify emergence respects the same structural bounds as all modules."""

    @classmethod
    def setUpClass(cls):
        cls.mod = importlib.import_module(TARGET_MODULE)
        cls.summary = cls.mod.framework_summary()

    def test_kv4_composite_below_one(self):
        """KV₄: composite convergence must be strictly < 1.0."""
        composite = self.summary.get("composite_score", self.summary.get("composite"))
        self.assertIsNotNone(composite, "framework_summary must include composite score")
        self.assertLess(composite, 1.0, "Composite ≥ 1.0 is a KV₄ violation")

    def test_kv4_composite_above_zero(self):
        """KV₄: composite must be > 0 (non-trivial engagement)."""
        composite = self.summary.get("composite_score", self.summary.get("composite"))
        self.assertGreater(composite, 0.0, "Composite = 0 means no engagement")

    def test_kv4_flag_high_convergence(self):
        """KV₄: composite ≥ 0.95 is an error, not a success."""
        composite = self.summary.get("composite_score", self.summary.get("composite"))
        self.assertLess(composite, 0.95, "Composite ≥ 0.95 is a KV₄ error flag")

    def test_ax52_multiplicative_gate(self):
        """AX52: zero in any dimension collapses the gate."""
        gate = self.summary.get("gate_pass", self.summary.get("gate"))
        self.assertIsNotNone(gate, "framework_summary must include gate status")
        self.assertTrue(gate, "Multiplicative gate should pass on default DAG")

    def test_yakinlasma_below_max_structural_completeness(self):
        """T17/AX56: yakinlasma score ≤ 0.9999 (MAX_STRUCTURAL_COMPLETENESS)."""
        score = self.mod.yakinlasma()
        self.assertLessEqual(score, 0.9999,
                             "yakinlasma exceeds MAX_STRUCTURAL_COMPLETENESS")


# =====================================================================
#  3. fw_doctor Visibility
# =====================================================================

class TestFwDoctorVisibility(unittest.TestCase):
    """Verify emergence is registered in fw_doctor's check lists."""

    def test_in_required_packages(self):
        from fw_doctor.checks import REQUIRED_PACKAGES
        pkg_names = [pkg for pkg, _ in REQUIRED_PACKAGES]
        self.assertIn("emergence", pkg_names,
                      "emergence must be in fw_doctor REQUIRED_PACKAGES")

    def test_in_module_summaries(self):
        from fw_doctor.checks import MODULE_SUMMARIES
        mod_paths = [path for path, _ in MODULE_SUMMARIES]
        self.assertIn("emergence.emergence", mod_paths,
                      "emergence.emergence must be in fw_doctor MODULE_SUMMARIES")

    def test_fw_doctor_import_check_passes(self):
        """Simulate fw_doctor's import check for emergence."""
        try:
            importlib.import_module("emergence")
        except ImportError as e:
            self.fail(f"emergence import failed: {e}")

    def test_fw_doctor_summary_check_passes(self):
        """Simulate fw_doctor's framework_summary check for emergence."""
        mod = importlib.import_module("emergence.emergence")
        fn = getattr(mod, "framework_summary", None)
        self.assertIsNotNone(fn, "framework_summary must exist")
        summary = fn()
        self.assertIsInstance(summary, dict)
        self.assertGreater(len(summary), 0)


# =====================================================================
#  4. KV₇ Independence (no cross-module imports)
# =====================================================================

class TestKV7Independence(unittest.TestCase):
    """Verify emergence imports ONLY from stdlib — no cross-module deps."""

    STDLIB_MODULES = frozenset({
        "enum", "math", "dataclasses", "typing", "collections",
        "functools", "itertools", "abc", "copy", "json", "re",
        "os", "sys", "pathlib", "io", "decimal", "fractions",
        "statistics", "logging", "time", "datetime", "operator",
        "__future__",
    })

    # Modules from this project that emergence must NOT import
    PROJECT_MODULES = frozenset({
        "bileshke", "kavram_sozlugu", "mereoloji", "fol_formalizasyon",
        "bayes_analiz", "oyun_teorisi", "kategori_teorisi", "holografik",
        "hcp", "kaskad", "fidelity_funnel", "fw_server", "fw_doctor",
    })

    def test_no_project_imports_in_source(self):
        """Scan emergence.py source for project module imports."""
        import pathlib
        src = pathlib.Path(__file__).parent.parent / "emergence" / "emergence.py"
        text = src.read_text(encoding="utf-8")

        violations = []
        for proj_mod in self.PROJECT_MODULES:
            # Match "import proj_mod" or "from proj_mod"
            pattern = rf'\b(?:import|from)\s+{re.escape(proj_mod)}\b'
            if re.search(pattern, text):
                violations.append(proj_mod)

        self.assertEqual(violations, [],
                         f"KV₇ violation: emergence.py imports project modules: {violations}")

    def test_no_third_party_imports(self):
        """Scan emergence.py for non-stdlib imports (numpy, etc.)."""
        import pathlib
        src = pathlib.Path(__file__).parent.parent / "emergence" / "emergence.py"
        text = src.read_text(encoding="utf-8")

        import_pattern = re.compile(
            r'^\s*(?:import|from)\s+(\w+)', re.MULTILINE
        )
        imported = {m.group(1) for m in import_pattern.finditer(text)}

        # Remove stdlib and known safe imports
        non_stdlib = imported - self.STDLIB_MODULES
        self.assertEqual(non_stdlib, set(),
                         f"KV₇ violation: non-stdlib imports found: {non_stdlib}")


# =====================================================================
#  5. Output Shape Parity
# =====================================================================

class TestOutputShapeParity(unittest.TestCase):
    """Compare emergence's framework_summary output against reference modules.

    This tests that emergence's summary has meaningful overlap with the
    canonical output shape, ensuring it speaks the same 'language' as
    the rest of the system.
    """

    @classmethod
    def setUpClass(cls):
        """Collect framework_summary outputs from all available modules."""
        cls.summaries: Dict[str, dict] = {}
        cls.key_inventory: Dict[str, set] = {}

        # Collect reference module summaries
        for mod_path, label, _, _, has_summary in REFERENCE_MODULES:
            if not has_summary:
                continue
            try:
                mod = importlib.import_module(mod_path)
                fn = getattr(mod, "framework_summary", None)
                if fn:
                    s = fn()
                    if isinstance(s, dict):
                        cls.summaries[label] = s
                        cls.key_inventory[label] = set(s.keys())
            except Exception:
                pass  # Skip unavailable modules

        # Collect emergence summary
        try:
            mod = importlib.import_module(TARGET_MODULE)
            s = mod.framework_summary()
            cls.summaries[TARGET_LABEL] = s
            cls.key_inventory[TARGET_LABEL] = set(s.keys())
        except Exception:
            pass

    def test_emergence_summary_exists(self):
        self.assertIn(TARGET_LABEL, self.summaries,
                      "emergence framework_summary() must be callable")

    def test_has_module_identifier(self):
        """Every module's summary should identify itself."""
        s = self.summaries.get(TARGET_LABEL, {})
        has_id = any(k in s for k in ("module", "name", "module_name", "lens_name"))
        self.assertTrue(has_id,
                        f"emergence summary should identify itself. Keys: {list(s.keys())}")

    def test_has_checks_info(self):
        """Every module's summary should report verification check counts."""
        s = self.summaries.get(TARGET_LABEL, {})
        has_checks = any(
            k in s for k in (
                "checks_passed", "checks_total", "checks",
                "pass_count", "total_checks",
            )
        )
        self.assertTrue(has_checks,
                        f"emergence summary should report check counts. Keys: {list(s.keys())}")

    def test_has_composite_or_score(self):
        """Every module's summary should include a composite or score."""
        s = self.summaries.get(TARGET_LABEL, {})
        has_score = any(
            k in s for k in (
                "composite", "composite_score", "score",
                "yakinlasma", "convergence",
            )
        )
        self.assertTrue(has_score,
                        f"emergence summary should include score. Keys: {list(s.keys())}")

    def test_common_key_overlap(self):
        """Emergence should share at least some keys with reference modules."""
        if len(self.key_inventory) < 2:
            self.skipTest("Not enough reference modules available")

        emergence_keys = self.key_inventory.get(TARGET_LABEL, set())
        ref_keys_union = set()
        for label, keys in self.key_inventory.items():
            if label != TARGET_LABEL:
                ref_keys_union |= keys

        overlap = emergence_keys & ref_keys_union
        # At least 2 keys should overlap (module, checks, score, etc.)
        self.assertGreaterEqual(
            len(overlap), 2,
            f"emergence shares only {len(overlap)} key(s) with reference modules: {overlap}"
        )

    def test_key_inventory_report(self):
        """Print a diagnostic comparison (always passes, informational)."""
        emergence_keys = sorted(self.key_inventory.get(TARGET_LABEL, set()))
        print(f"\n{'='*60}")
        print(f"  EMERGENCE summary keys: {emergence_keys}")
        for label, keys in sorted(self.key_inventory.items()):
            if label != TARGET_LABEL:
                shared = sorted(set(emergence_keys) & keys)
                print(f"  {label:25s}: shared={shared}")
        print(f"{'='*60}")


# =====================================================================
#  6. Cross-Module verify_all Return-Type Parity
# =====================================================================

class TestVerifyAllParity(unittest.TestCase):
    """Compare emergence's verify_all() shape against reference modules."""

    def test_same_return_type_as_references(self):
        """All verify_all implementations should return Dict[str, Tuple[bool, str]]."""
        target_mod = importlib.import_module(TARGET_MODULE)
        target_result = target_mod.verify_all()

        for mod_path, label, has_verify_all, _, _ in REFERENCE_MODULES:
            if not has_verify_all:
                continue
            try:
                ref_mod = importlib.import_module(mod_path)
                ref_fn = getattr(ref_mod, "verify_all", None)
                if ref_fn is None:
                    continue

                # Some verify_all take args, call with defaults only
                sig = inspect.signature(ref_fn)
                # If it requires positional args, skip
                required = [
                    p for p in sig.parameters.values()
                    if p.default is inspect.Parameter.empty
                    and p.kind in (
                        inspect.Parameter.POSITIONAL_ONLY,
                        inspect.Parameter.POSITIONAL_OR_KEYWORD,
                    )
                ]
                if required:
                    continue

                ref_result = ref_fn()
                # Both should be dicts
                self.assertIsInstance(
                    ref_result, dict,
                    f"{label} verify_all should return dict"
                )
                self.assertIsInstance(
                    target_result, dict,
                    "emergence verify_all should return dict"
                )

                # Both should have (bool, str) tuples as values
                if ref_result:
                    ref_val = next(iter(ref_result.values()))
                    self.assertIsInstance(ref_val, tuple,
                                         f"{label} verify_all values should be tuples")

            except Exception:
                continue  # Skip modules that can't be tested this way


# =====================================================================
#  7. Structural Invariant Cross-Check
# =====================================================================

class TestStructuralInvariantCrossCheck(unittest.TestCase):
    """Verify that emergence's structural invariants match the system's."""

    def test_max_score_constant_matches_clamp(self):
        """MAX_STRUCTURAL_COMPLETENESS should be used by clamp_score."""
        from emergence.emergence import clamp_score, MAX_STRUCTURAL_COMPLETENESS
        val = clamp_score(999.0)
        self.assertAlmostEqual(val, MAX_STRUCTURAL_COMPLETENESS,
                               msg="clamp_score must cap at MAX_STRUCTURAL_COMPLETENESS")

    def test_bileshke_clamp_score_same_contract(self):
        """emergence.clamp_score and bileshke.clamp_score should behave identically."""
        from emergence.emergence import clamp_score as e_clamp
        try:
            from bileshke.types import clamp_score as b_clamp
        except ImportError:
            self.skipTest("bileshke not available")

        test_values = [-1.0, 0.0, 0.5, 0.99, 1.0, 1.5, 100.0]
        for v in test_values:
            e_val = e_clamp(v)
            b_val = b_clamp(v)
            # Both should clamp to [0, <1.0)
            self.assertGreaterEqual(e_val, 0.0,
                                    f"emergence clamp_score({v}) < 0")
            self.assertLess(e_val, 1.0,
                            f"emergence clamp_score({v}) >= 1.0")
            self.assertGreaterEqual(b_val, 0.0,
                                    f"bileshke clamp_score({v}) < 0")
            self.assertLess(b_val, 1.0,
                            f"bileshke clamp_score({v}) >= 1.0")


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