"""
P20 Neuroscience Refinements — Source Inspection Tests
Items 81-85: Feedback loop closures.
All tests read file text only; zero live DB calls.
"""
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent

# File paths under test
STAGE_MINING = ROOT / "pulse/daemons/consolidation/stages/stage_mining.py"
SEARCH_ENGINE = ROOT / "pulse/brain/brain_search/search_engine.py"
AGENT_PROCS = ROOT / "pulse/brain/agent_procedures.py"
TASK_CLAIMING = ROOT / "pulse/tasks/task_claiming.py"
METAMEMORY = ROOT / "pulse/brain/metamemory.py"
REWARD_SIGNAL = ROOT / "pulse/core/reward_signal.py"


def _read(path: Path) -> str:
    return path.read_text(encoding="utf-8")


# ---------------------------------------------------------------------------
# Item 81: Episodic → Semantic graduation in mining stage
# ---------------------------------------------------------------------------

class TestItem81EpisodicGraduation:
    def test_stage_function_defined(self):
        src = _read(STAGE_MINING)
        assert "_stage_episodic_graduation" in src

    def test_public_alias_exported(self):
        src = _read(STAGE_MINING)
        assert "stage_episodic_graduation = _stage_episodic_graduation" in src

    def test_queries_episodic_type_with_threshold(self):
        src = _read(STAGE_MINING)
        assert "type = 'episodic'" in src or "type = \"episodic\"" in src
        assert "access_count >= 5" in src
        assert "confidence >= 0.7" in src

    def test_calls_check_graduation(self):
        src = _read(STAGE_MINING)
        assert "_check_graduation" in src

    def test_imports_fact_store(self):
        src = _read(STAGE_MINING)
        assert "from pulse.brain.persistence.fact_store import _check_graduation" in src

    def test_imports_brain_db(self):
        src = _read(STAGE_MINING)
        assert "from pulse.core.brain_db import get_conn" in src

    def test_returns_status_ok(self):
        src = _read(STAGE_MINING)
        # Check the function has a result dict with 'status': 'ok'
        assert "'status': 'ok'" in src or '"status": "ok"' in src

    def test_fail_open_try_except(self):
        src = _read(STAGE_MINING)
        # Must have at least one fail-open except inside _stage_episodic_graduation
        fn_start = src.find("def _stage_episodic_graduation")
        fn_end = src.find("\ndef ", fn_start + 1)
        fn_body = src[fn_start:fn_end if fn_end > 0 else len(src)]
        assert "except Exception" in fn_body

    def test_tracks_graduated_count(self):
        src = _read(STAGE_MINING)
        assert "graduated" in src

    def test_under_300_lines(self):
        lines = _read(STAGE_MINING).splitlines()
        assert len(lines) <= 300, f"stage_mining.py is {len(lines)} lines (limit 300)"


# ---------------------------------------------------------------------------
# Item 82: Reward-gated search ranking — credit ledger feedback
# ---------------------------------------------------------------------------

class TestItem82RewardGatedRanking:
    def test_reward_boosted_flag_present(self):
        src = _read(SEARCH_ENGINE)
        assert "_reward_boosted" in src

    def test_reward_penalized_flag_present(self):
        src = _read(SEARCH_ENGINE)
        assert "_reward_penalized" in src

    def test_queries_retrieval_metrics_table(self):
        src = _read(SEARCH_ENGINE)
        assert "retrieval_metrics" in src

    def test_queries_retrieval_count_column(self):
        src = _read(SEARCH_ENGINE)
        assert "retrieval_count" in src

    def test_uses_lazy_db_import(self):
        src = _read(SEARCH_ENGINE)
        assert "_gc82" in src

    def test_confidence_boost_formula(self):
        src = _read(SEARCH_ENGINE)
        # Check boost: +0.05 * min(access, 5)
        assert "0.05 * min(" in src

    def test_confidence_penalty_formula(self):
        src = _read(SEARCH_ENGINE)
        assert "0.85" in src

    def test_fail_open_around_reward_block(self):
        src = _read(SEARCH_ENGINE)
        assert "except Exception: pass" in src or "except Exception:\n                pass" in src

    def test_checks_access_count(self):
        src = _read(SEARCH_ENGINE)
        assert "_acc82" in src or "access_count" in src

    def test_under_300_lines(self):
        lines = _read(SEARCH_ENGINE).splitlines()
        assert len(lines) <= 300, f"search_engine.py is {len(lines)} lines (limit 300)"


# ---------------------------------------------------------------------------
# Item 83: Cross-agent procedure conflict detection
# ---------------------------------------------------------------------------

class TestItem83CrossAgentConflict:
    def test_conflict_detection_block_present(self):
        src = _read(AGENT_PROCS)
        assert "_conflict_with" in src

    def test_uses_text_similarity(self):
        src = _read(AGENT_PROCS)
        assert "text_similarity" in src

    def test_checks_source_agent_differs(self):
        src = _read(AGENT_PROCS)
        assert "source_agent" in src

    def test_similarity_bounds_correct(self):
        src = _read(AGENT_PROCS)
        # Conflict band: 0.3 < sim < 0.7
        assert "0.3" in src and "0.7" in src

    def test_marks_both_procedures(self):
        src = _read(AGENT_PROCS)
        # Both _pa and _pb get _conflict_with set
        conflict_matches = re.findall(r'_conflict_with', src)
        assert len(conflict_matches) >= 2

    def test_checks_same_domain(self):
        src = _read(AGENT_PROCS)
        assert '"domain"' in src

    def test_fail_open_on_conflict_detection(self):
        src = _read(AGENT_PROCS)
        fn_start = src.find("# Item 83")
        fn_end = src.find("scored.sort", fn_start)
        block = src[fn_start:fn_end]
        assert "except Exception" in block

    def test_under_300_lines(self):
        lines = _read(AGENT_PROCS).splitlines()
        assert len(lines) <= 300, f"agent_procedures.py is {len(lines)} lines (limit 300)"


# ---------------------------------------------------------------------------
# Item 84: Predictive pre-fetch on task claim
# ---------------------------------------------------------------------------

class TestItem84PredictivePrefetch:
    def test_prefetch_block_present(self):
        src = _read(TASK_CLAIMING)
        assert "Item 84" in src

    def test_imports_write_hot(self):
        src = _read(TASK_CLAIMING)
        assert "write_hot" in src

    def test_queries_lessons_table(self):
        src = _read(TASK_CLAIMING)
        assert "lessons" in src
        # Must query by domain
        assert "_dom84" in src or "domain" in src

    def test_queries_high_confidence_items(self):
        src = _read(TASK_CLAIMING)
        assert "confidence>=0.7" in src or "confidence >= 0.7" in src

    def test_uses_brain_db(self):
        src = _read(TASK_CLAIMING)
        assert "_gc84" in src

    def test_prefetch_after_atomic_claim(self):
        src = _read(TASK_CLAIMING)
        claim_pos = src.find("atomic(board_path, _do_claim")
        prefetch_pos = src.find("Item 84")
        assert prefetch_pos > claim_pos, "Prefetch must come after atomic claim call"

    def test_fail_open_prefetch(self):
        src = _read(TASK_CLAIMING)
        item84_start = src.find("# Item 84")
        next_section = src.find("\n    def ", item84_start)
        if next_section < 0:
            next_section = src.find("\n\ndef ", item84_start)
        block = src[item84_start: next_section if next_section > 0 else item84_start + 500]
        assert "except Exception" in block

    def test_sets_minimum_salience_for_hot_cache(self):
        src = _read(TASK_CLAIMING)
        # Must boost salience to at least 4.0 (hot cache threshold)
        assert "4.0" in src

    def test_under_300_lines(self):
        lines = _read(TASK_CLAIMING).splitlines()
        assert len(lines) <= 300, f"task_claiming.py is {len(lines)} lines (limit 300)"


# ---------------------------------------------------------------------------
# Item 85: Calibration feedback loop
# ---------------------------------------------------------------------------

class TestItem85CalibrationFeedback:
    def test_calibration_history_list_defined(self):
        src = _read(METAMEMORY)
        assert "_CALIBRATION_HISTORY" in src

    def test_calibration_window_constant(self):
        src = _read(METAMEMORY)
        assert "_CALIBRATION_WINDOW" in src
        assert "50" in src

    def test_record_calibration_function(self):
        src = _read(METAMEMORY)
        assert "def _record_calibration" in src

    def test_get_calibration_factor_function(self):
        src = _read(METAMEMORY)
        assert "def _get_calibration_factor" in src

    def test_calibration_factor_clamps_range(self):
        src = _read(METAMEMORY)
        assert "min(1.5" in src or "max(0.5" in src

    def test_returns_1_when_insufficient_data(self):
        src = _read(METAMEMORY)
        # Must check len < 10 and return 1.0
        assert "< 10" in src
        assert "return 1.0" in src

    def test_record_calibration_wired_into_reward_signal(self):
        src = _read(REWARD_SIGNAL)
        assert "_record_calibration" in src

    def test_calibration_call_uses_expected_and_success(self):
        src = _read(REWARD_SIGNAL)
        # The wire must pass expected (predicted confidence) and success bool
        assert "_rc85(expected, success)" in src or "_record_calibration" in src

    def test_calibration_wire_is_fail_open(self):
        src = _read(REWARD_SIGNAL)
        item85_pos = src.find("Item 85")
        block = src[item85_pos: item85_pos + 200]
        assert "except Exception" in block

    def test_calibration_factor_applied_in_search(self):
        src = _read(SEARCH_ENGINE)
        assert "_get_calibration_factor" in src

    def test_search_calibration_wire_fail_open(self):
        src = _read(SEARCH_ENGINE)
        gcf_pos = src.find("_get_calibration_factor")
        block = src[max(0, gcf_pos - 50): gcf_pos + 200]
        assert "except Exception" in block

    def test_metamemory_under_300_lines(self):
        lines = _read(METAMEMORY).splitlines()
        assert len(lines) <= 300, f"metamemory.py is {len(lines)} lines (limit 300)"

    def test_reward_signal_under_300_lines(self):
        lines = _read(REWARD_SIGNAL).splitlines()
        assert len(lines) <= 300, f"reward_signal.py is {len(lines)} lines (limit 300)"
