"""P14 Neuroscience Refinements — Tests (Items 51-55).

All tests use source-code inspection only. NO live DB calls.
"""
import importlib, inspect, sys, types
from pathlib import Path

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

# ---------------------------------------------------------------------------
# Minimal stubs so target modules can be *imported* without side effects
# ---------------------------------------------------------------------------
def _stub(name, attrs=None):
    if name not in sys.modules:
        m = types.ModuleType(name)
        for k, v in (attrs or {}).items():
            setattr(m, k, v)
        sys.modules[name] = m
    return sys.modules[name]

_stub("pulse")
_stub("pulse.core")
_stub("pulse.brain")
_stub("pulse.brain.save_writers")
_stub("pulse.core.paths", {"get_state_dir": lambda: Path("."), "get_brain_dir": lambda: Path(".")})
_stub("pulse.core.brain_utils", {
    "TASK_BOARD_FILE": Path("tb.json"),
    "AGENT_REGISTRY_FILE": Path("r.json"),
    "HIPPOCAMPUS_DIR": Path("."),
    "load_json_dict": lambda f: {},
    "_make_fingerprint": lambda s: "fp",
    "atomic_update_json": lambda *a, **kw: None,
    "now_iso": lambda: "2026-01-01T00:00:00Z",
})
_stub("pulse.core.brain_io", {"_acquire": lambda p: __import__("contextlib").nullcontext()})
_stub("pulse.core.interoception", {"get_signals": lambda: {}})
_stub("pulse.core.event_bus", {"publish": lambda *a, **kw: None})
_stub("pulse.core.temporal", {"stamp_temporal_fields": lambda e, t: e})
_stub("pulse.core.uncertainty", {"stamp_uncertainty_fields": lambda e: e})
_stub("pulse.core.valence", {"compute_valence": lambda t: (0.0, 0.0)})
_stub("pulse.brain.metacognition.metamemory", {"get_metamemory": lambda: {}})
_stub("pulse.brain.index.rule_embeddings", {"embed_texts": lambda t: [], "EMBED_INDEX_FILE": Path("no.idx")})
_stub("pulse.brain.reconsolidation", {
    "reinforce": lambda *a, **kw: None,
    "contradict": lambda *a, **kw: None,
    "mark_labile": lambda *a, **kw: None,
})
_stub("pulse.core.reward.volatility", {"get_volatility_multiplier": lambda: 1.0})
_stub("pulse.brain.integrity.grounding", {"maybe_promote": lambda *a, **kw: None})
_stub("pulse.brain.metacognition.learned_prompt", {"update_from_credits": lambda *a, **kw: None})
_stub("pulse.brain.hebbian_knowledge", {"apply_hebbian": lambda *a, **kw: None})
_stub("pulse.brain.stdp", {
    "apply_stdp": lambda *a, **kw: 0,
    "_apply_reward_modulation": lambda *a, **kw: None,
    "_BURST_TIMESTAMPS": [],
})
_stub("pulse.brain.resurrection", {"check_resurrection": lambda *a, **kw: None})
_stub("pulse.tasks.decomposer", {"decompose_prompt": lambda *a, **kw: []})
_stub("pulse.tasks.dispatcher_lifecycle", {
    "check_stale_claims": lambda *a, **kw: False,
    "unblock_dependents": lambda *a, **kw: False,
    "apply_capability_routing": lambda *a, **kw: False,
    "archive_completed": lambda *a, **kw: False,
    "gc_stale_dispatches": lambda *a, **kw: False,
    "score_agents_for_task": lambda *a, **kw: [],
})
_stub("pulse.tasks.evidence_accumulator", {"select_best_agent": lambda c, t: (c[0] if c else None, {})})

# ---------------------------------------------------------------------------
# Import target modules (source inspection only — no DB calls)
# ---------------------------------------------------------------------------
import importlib.util as _ilu

def _load(rel_path):
    p = ROOT / rel_path
    spec = _ilu.spec_from_file_location(rel_path.replace("/", "."), p)
    mod = _ilu.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod

reward_signal = _load("pulse/core/reward/reward_signal.py")
pulse_dispatcher = _load("pulse/tasks/pulse_dispatcher.py")
jsonl_store = _load("pulse/brain/save_writers/jsonl_store.py")
stdp = _load("pulse/brain/plasticity/stdp.py")
brain_db = _load("pulse/core/brain_db.py")


# ===========================================================================
# Item 51: Agent-specific RPE
# ===========================================================================

def test_51_agent_reward_history_dict_exists():
    """_AGENT_REWARD_HISTORY module-level dict must exist."""
    assert hasattr(reward_signal, "_AGENT_REWARD_HISTORY"), "_AGENT_REWARD_HISTORY missing"
    assert isinstance(reward_signal._AGENT_REWARD_HISTORY, dict)


def test_51_agent_rpe_window_constant():
    """_AGENT_RPE_WINDOW constant must be set (20)."""
    assert hasattr(reward_signal, "_AGENT_RPE_WINDOW")
    assert reward_signal._AGENT_RPE_WINDOW == 20


def test_51_rpe_logic_in_record_task_outcome():
    """record_task_outcome source must reference _AGENT_REWARD_HISTORY and agent_rpe."""
    src = inspect.getsource(reward_signal.record_task_outcome)
    assert "_AGENT_REWARD_HISTORY" in src
    assert "_arpe" in src or "agent_rpe" in src


def test_51_rpe_multiplier_on_surprise():
    """rpe_delta must be scaled by abs(agent_rpe) when surprise > 0.3 threshold."""
    src = inspect.getsource(reward_signal.record_task_outcome)
    assert "0.3" in src          # threshold present
    assert "rpe_delta *=" in src or "rpe_delta *= " in src


def test_51_fail_open():
    """Item 51 block must have a bare except/pass for fail-open safety."""
    src = inspect.getsource(reward_signal.record_task_outcome)
    # Ensure there is an except+pass near the agent reward history code
    assert "except Exception" in src and "pass" in src


# ===========================================================================
# Item 52: Runner-up agent counterfactual
# ===========================================================================

def test_52_runner_up_key_in_dispatch_src():
    """dispatch() source must set _runner_up on tasks."""
    src = inspect.getsource(pulse_dispatcher.TaskDispatcher.dispatch)
    assert "_runner_up" in src


def test_52_runner_up_score_key_in_dispatch_src():
    """dispatch() source must also store _runner_up_score."""
    src = inspect.getsource(pulse_dispatcher.TaskDispatcher.dispatch)
    assert "_runner_up_score" in src


def test_52_sorted_candidates_used():
    """Runner-up requires sorting candidates by score descending."""
    src = inspect.getsource(pulse_dispatcher.TaskDispatcher.dispatch)
    assert "sorted" in src or "sort" in src


def test_52_fail_open():
    """Runner-up block must have except/pass for fail-open safety."""
    src = inspect.getsource(pulse_dispatcher.TaskDispatcher.dispatch)
    assert "except Exception" in src
    assert "pass" in src


def test_52_at_least_two_candidates_check():
    """Must guard that there are >= 2 candidates before storing runner-up."""
    src = inspect.getsource(pulse_dispatcher.TaskDispatcher.dispatch)
    assert ">= 2" in src or "len(sorted" in src or "len(scores" in src


# ===========================================================================
# Item 53: Social failure broadcast
# ===========================================================================

def test_53_broadcast_key_set_on_high_confidence_failure():
    """build_jsonl_entry source must set broadcast=True for high-confidence failures."""
    src = inspect.getsource(jsonl_store.build_jsonl_entry)
    assert "broadcast" in src


def test_53_social_broadcast_key_present():
    """_social_broadcast flag must also be set."""
    src = inspect.getsource(jsonl_store.build_jsonl_entry)
    assert "_social_broadcast" in src


def test_53_confidence_threshold_08():
    """Broadcast threshold must be 0.8 for high-confidence failures."""
    src = inspect.getsource(jsonl_store.build_jsonl_entry)
    assert "0.8" in src


def test_53_only_for_failure_type():
    """Broadcast must only apply to failure-type entries."""
    src = inspect.getsource(jsonl_store.build_jsonl_entry)
    assert '"failure"' in src or "'failure'" in src


def test_53_fail_open():
    """Social broadcast block must be wrapped in try/except."""
    src = inspect.getsource(jsonl_store.build_jsonl_entry)
    assert "except Exception" in src
    assert "pass" in src


# ===========================================================================
# Item 55: Critical period for new domains
# ===========================================================================

def test_55_critical_period_domains_dict_exists():
    """_CRITICAL_PERIOD_DOMAINS module-level dict must exist."""
    assert hasattr(brain_db, "_CRITICAL_PERIOD_DOMAINS")
    assert isinstance(brain_db._CRITICAL_PERIOD_DOMAINS, dict)


def test_55_critical_period_size_constant():
    """_CRITICAL_PERIOD_SIZE must equal 10."""
    assert hasattr(brain_db, "_CRITICAL_PERIOD_SIZE")
    assert brain_db._CRITICAL_PERIOD_SIZE == 10


def test_55_critical_period_in_insert_knowledge_src():
    """insert_knowledge must reference _CRITICAL_PERIOD_DOMAINS."""
    src = inspect.getsource(brain_db.insert_knowledge)
    assert "_CRITICAL_PERIOD_DOMAINS" in src


def test_55_salience_doubled():
    """Critical period logic must multiply salience by 2.0."""
    src = inspect.getsource(brain_db.insert_knowledge)
    assert "2.0" in src or "* 2" in src


def test_55_countdown_decrements():
    """Critical period counter must decrement (-= 1) per item."""
    src = inspect.getsource(brain_db.insert_knowledge)
    assert "-= 1" in src


def test_55_domain_removed_when_expired():
    """Domain must be deleted from _CRITICAL_PERIOD_DOMAINS when counter reaches 0."""
    src = inspect.getsource(brain_db.insert_knowledge)
    assert "del _CRITICAL_PERIOD_DOMAINS" in src


def test_55_fail_open():
    """Critical period block must be wrapped in try/except pass."""
    src = inspect.getsource(brain_db.insert_knowledge)
    assert "except Exception" in src
    assert "pass" in src
