"""phase4 neuroscience plasticity spreading adversarial inhibition boundary interoception metamemory coactivation salience bcm_threshold ior_penalty

Source-inspection tests for 5 neuroscience refinements.
All assertions check that the correct logic was injected without
requiring a live SQLite DB or running LLMs.
"""
import inspect
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))


# ---------------------------------------------------------------------------
# Item 1: BCM sliding plasticity threshold in brain_db.insert_knowledge
# ---------------------------------------------------------------------------

def test_bcm_gate_references_interoception():
    """insert_knowledge source must reference interoception get_signals and BCM gate."""
    import pulse.core.brain_db as mod
    src = inspect.getsource(mod.insert_knowledge)
    assert "interoception" in src, "insert_knowledge must import from pulse.core.interoception"
    assert "get_signals" in src, "insert_knowledge must call get_signals()"
    assert "agent_load" in src, "insert_knowledge must check agent_load signal"
    assert "BCM gate" in src, "insert_knowledge must have BCM gate comment"


def test_bcm_gate_fail_open():
    """insert_knowledge must have a bare except that passes (fail-open)."""
    import pulse.core.brain_db as mod
    src = inspect.getsource(mod.insert_knowledge)
    assert "except Exception:" in src
    except_pos = src.index("except Exception:")
    pass_pos = src.index("pass", except_pos)
    assert pass_pos > except_pos, "fail-open pass must follow the except block"


def test_bcm_gate_return_none_on_high_load():
    """BCM gate must return None (not raise) when load > 5 and salience < 3.0."""
    import pulse.core.brain_db as mod
    src = inspect.getsource(mod.insert_knowledge)
    assert "return None" in src, "BCM gate must return None to block low-salience inserts"
    assert "3.0" in src, "BCM threshold must check salience against 3.0"


# ---------------------------------------------------------------------------
# Item 2: Spreading activation via COACTIVATION edges in search_engine
# ---------------------------------------------------------------------------

def test_spreading_activation_references_coactivation():
    """brain_search source must reference COACTIVATION edge traversal."""
    import pulse.brain.brain_search.search_engine as mod
    src = inspect.getsource(mod.brain_search)
    assert "COACTIVATION" in src, "brain_search must traverse COACTIVATION edges"
    assert "spreading_activation" in src, "brain_search must populate spreading_activation key"


def test_spreading_activation_uses_graph_edges():
    """Spreading activation must query graph_edges table."""
    import pulse.brain.brain_search.search_engine as mod
    src = inspect.getsource(mod.brain_search)
    assert "graph_edges" in src, "spreading activation must query graph_edges table"
    assert "confidence > 0.5" in src, "spreading activation must filter edges by confidence"


def test_spreading_activation_fail_open():
    """Spreading activation block must be wrapped in try/except (fail-open)."""
    import pulse.brain.brain_search.search_engine as mod
    src = inspect.getsource(mod.brain_search)
    sa_pos = src.index("Spreading activation")
    except_pos = src.index("except Exception:", sa_pos)
    assert except_pos > sa_pos, "spreading activation must be wrapped in try/except"


# ---------------------------------------------------------------------------
# Item 3: Expert adversarial counterexample in briefing.py
# ---------------------------------------------------------------------------

def test_expert_adversarial_references_metamemory():
    """format_briefing adversarial block must check metamemory for expert domains."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert "metamemory" in src, "format_briefing must import from pulse.brain.metacognition.metamemory"
    assert "get_metamemory" in src, "format_briefing must call get_metamemory()"
    assert "competence_level" in src, "format_briefing must check competence_level"
    assert "expert" in src, "format_briefing must branch on 'expert' competence"


def test_expert_adversarial_fetches_variable_count():
    """format_briefing must fetch num_adversarial failures, not hardcoded LIMIT 1."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert "num_adversarial" in src, "format_briefing must use num_adversarial variable"
    assert "LIMIT ?" in src, "format_briefing must use parameterised LIMIT"


def test_expert_adversarial_defaults_to_one():
    """num_adversarial must default to 1 when metamemory lookup fails."""
    import pulse.brain.brain_query.briefing as mod
    src = inspect.getsource(mod.format_briefing)
    assert src.count("num_adversarial = 1") >= 2, (
        "num_adversarial must be set to 1 both before the try and in the except fallback"
    )


# ---------------------------------------------------------------------------
# Item 4: Inhibition of Return in working_memory.py
# ---------------------------------------------------------------------------

def test_ior_mark_broadcast_exists():
    """working_memory must define _mark_broadcast function."""
    import pulse.brain.memory.working_memory as mod
    assert hasattr(mod, "_mark_broadcast"), "working_memory must define __mark_broadcast()"
    assert callable(mod._mark_broadcast)


def test_ior_get_penalty_exists():
    """working_memory must define _get_ior_penalty function."""
    import pulse.brain.memory.working_memory as mod
    assert hasattr(mod, "_get_ior_penalty"), "working_memory must define __get_ior_penalty()"
    assert callable(mod._get_ior_penalty)


def test_ior_recently_broadcast_dict_exists():
    """working_memory must have module-level _RECENTLY_BROADCAST dict."""
    import pulse.brain.memory.working_memory as mod
    assert hasattr(mod, "_RECENTLY_BROADCAST"), "working_memory must have _RECENTLY_BROADCAST"
    assert isinstance(mod._RECENTLY_BROADCAST, dict)


def test_ior_mark_and_penalty_round_trip():
    """mark_broadcast then get_ior_penalty must return 0.15 within 30s window (N47: reduced from 0.3/60s)."""
    import pulse.brain.memory.working_memory as mod
    mod._RECENTLY_BROADCAST.clear()
    mod._mark_broadcast(["test-item-abc"])
    penalty = mod._get_ior_penalty("test-item-abc")
    assert penalty == 0.15, f"Expected IOR penalty 0.15 (N47), got {penalty}"


def test_ior_no_penalty_for_unknown_id():
    """get_ior_penalty must return 0.0 for items not in _RECENTLY_BROADCAST."""
    import pulse.brain.memory.working_memory as mod
    penalty = mod._get_ior_penalty("never-broadcast-xyz-999")
    assert penalty == 0.0, f"Expected 0.0 for unknown item, got {penalty}"


def test_ior_cleanup_removes_old_entries():
    """mark_broadcast must evict entries older than 30 seconds (N47)."""
    import time
    import pulse.brain.memory.working_memory as mod
    mod._RECENTLY_BROADCAST.clear()
    mod._RECENTLY_BROADCAST["stale-item"] = time.time() - 35  # 35s > 30s window
    mod._mark_broadcast(["fresh-item"])
    assert "stale-item" not in mod._RECENTLY_BROADCAST, "stale entries must be evicted"
    assert "fresh-item" in mod._RECENTLY_BROADCAST, "fresh entries must be retained"


# ---------------------------------------------------------------------------
# Item 5: DMN cognitive boundary detection in dmn_daemon.py
# ---------------------------------------------------------------------------

def test_dmn_is_cognitive_boundary_exists():
    """dmn_daemon must define _is_cognitive_boundary function."""
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    assert hasattr(mod, "_is_cognitive_boundary"), (
        "dmn_daemon must define _is_cognitive_boundary()"
    )
    assert callable(mod._is_cognitive_boundary)


def test_dmn_boundary_reads_task_board():
    """_is_cognitive_boundary source must read TASK_BOARD_FILE."""
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    src = inspect.getsource(mod._is_cognitive_boundary)
    assert "TASK_BOARD_FILE" in src, "_is_cognitive_boundary must read TASK_BOARD_FILE"
    assert "dispatches" in src, "_is_cognitive_boundary must iterate dispatches"
    assert "claimed" in src, "_is_cognitive_boundary must check for claimed status"


def test_dmn_boundary_fail_open():
    """_is_cognitive_boundary must return False on exception (fail-open)."""
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    src = inspect.getsource(mod._is_cognitive_boundary)
    assert "except Exception:" in src
    assert "return False" in src, "_is_cognitive_boundary must return False on error"


def test_dmn_boundary_returns_false_when_board_missing(tmp_path, monkeypatch):
    """_is_cognitive_boundary must return False when task board file is absent."""
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    import pulse.core.brain_utils as bu
    fake_path = tmp_path / "nonexistent_task_board.json"
    monkeypatch.setattr(bu, "TASK_BOARD_FILE", fake_path)
    result = mod._is_cognitive_boundary()
    assert result is False


def test_dmn_boundary_returns_true_when_no_active_claims(tmp_path, monkeypatch):
    """_is_cognitive_boundary must return True when all tasks are done/pending."""
    import json
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    import pulse.core.brain_utils as bu
    board_file = tmp_path / "task_board.json"
    board_file.write_text(json.dumps({
        "dispatches": [
            {"tasks": [{"status": "done"}, {"status": "pending"}]},
            {"tasks": [{"status": "done"}]},
        ]
    }), encoding="utf-8")
    monkeypatch.setattr(bu, "TASK_BOARD_FILE", board_file)
    result = mod._is_cognitive_boundary()
    assert result is True


def test_dmn_boundary_returns_false_when_active_claims_exist(tmp_path, monkeypatch):
    """_is_cognitive_boundary must return False when a task is still claimed."""
    import json
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    import pulse.core.brain_utils as bu
    board_file = tmp_path / "task_board.json"
    board_file.write_text(json.dumps({
        "dispatches": [
            {"tasks": [{"status": "claimed"}, {"status": "done"}]},
        ]
    }), encoding="utf-8")
    monkeypatch.setattr(bu, "TASK_BOARD_FILE", board_file)
    result = mod._is_cognitive_boundary()
    assert result is False


def test_dmn_dream_accepts_check_boundary_param():
    """dream() must accept check_boundary kwarg."""
    import pulse.daemons.synthesis.core.dmn_daemon as mod
    sig = inspect.signature(mod.dream)
    assert "check_boundary" in sig.parameters, (
        "dream() must accept check_boundary parameter"
    )
