"""Tests for P10 neuroscience refinements (items 31-35)."""
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))


# ──────────────────────────────────────────────────────────────────
# Item 31: DDM multi-round noisy sampling
# ──────────────────────────────────────────────────────────────────
from pulse.tasks.pulse_dispatcher import _ddm_select, _DDM_THRESHOLD, _DDM_NOISE, _DDM_MAX_ROUNDS


def test_ddm_returns_candidate():
    candidates = ["agent_a", "agent_b", "agent_c"]
    scores = {"agent_a": 0.9, "agent_b": 0.5, "agent_c": 0.3}
    result = _ddm_select(candidates, scores)
    assert result in candidates


def test_ddm_single_candidate():
    result = _ddm_select(["only_one"], {"only_one": 0.8})
    assert result == "only_one"


def test_ddm_favors_high_score_statistically():
    """Higher scored agent should win most of the time."""
    candidates = ["weak", "strong"]
    scores = {"weak": 0.1, "strong": 2.0}
    wins = sum(1 for _ in range(50) if _ddm_select(candidates, scores) == "strong")
    assert wins >= 35, f"Expected strong to win >=35/50 times, got {wins}"


def test_ddm_raises_on_empty():
    import pytest
    with pytest.raises((ValueError, Exception)):
        _ddm_select([], {})


def test_ddm_constants():
    assert _DDM_THRESHOLD > 0
    assert _DDM_NOISE > 0
    assert _DDM_MAX_ROUNDS > 0


def test_ddm_unknown_score_uses_default():
    """Agent not in scores dict should use 0.5 default drift."""
    candidates = ["known", "unknown"]
    scores = {"known": 0.5}
    # Should not raise
    result = _ddm_select(candidates, scores)
    assert result in candidates


# ──────────────────────────────────────────────────────────────────
# Item 32: Counterfactual regret on missed predictions
# ──────────────────────────────────────────────────────────────────

def test_counterfactual_regret_boosts_low_confidence():
    """Correct but under-confident prediction should get a confidence boost."""
    from unittest.mock import patch, MagicMock
    from pulse.daemons.neural import prediction_feedback as pf

    fake_alert = {
        "task_id": "t001",
        "status": "active",
        "confidence": 0.35,  # low confidence
        "prediction": "test prediction",
    }
    fake_data = {"alerts": [fake_alert]}

    def fake_atomic_update(path, updater, default=None):
        updater(fake_data)

    def fake_resolve_title(task_id):
        return ""

    with patch.object(pf, "atomic_update_json", side_effect=fake_atomic_update), \
         patch.object(pf, "_resolve_task_title", side_effect=fake_resolve_title), \
         patch.object(pf, "_update_metrics", return_value=None):
        pf.record_prediction_outcome("t001", success=False, agent_name="test")

    # Should have counterfactual regret applied
    assert fake_alert.get("_counterfactual_regret") is True
    # Confidence should be boosted above original 0.35
    assert fake_alert["confidence"] > 0.35


def test_counterfactual_regret_not_applied_high_confidence():
    """High-confidence correct prediction should NOT trigger regret."""
    from unittest.mock import patch
    from pulse.daemons.neural import prediction_feedback as pf

    fake_alert = {
        "task_id": "t002",
        "status": "active",
        "confidence": 0.85,  # high confidence
        "prediction": "high conf prediction",
    }
    fake_data = {"alerts": [fake_alert]}

    def fake_atomic_update(path, updater, default=None):
        updater(fake_data)

    with patch.object(pf, "atomic_update_json", side_effect=fake_atomic_update), \
         patch.object(pf, "_resolve_task_title", return_value=""), \
         patch.object(pf, "_update_metrics", return_value=None):
        pf.record_prediction_outcome("t002", success=False, agent_name="test")

    assert fake_alert.get("_counterfactual_regret") is not True


def test_counterfactual_regret_not_applied_on_false_positive():
    """When task succeeds (false positive), no counterfactual regret."""
    from unittest.mock import patch
    from pulse.daemons.neural import prediction_feedback as pf

    fake_alert = {
        "task_id": "t003",
        "status": "active",
        "confidence": 0.3,
        "prediction": "wrong prediction",
    }
    fake_data = {"alerts": [fake_alert]}

    def fake_atomic_update(path, updater, default=None):
        updater(fake_data)

    with patch.object(pf, "atomic_update_json", side_effect=fake_atomic_update), \
         patch.object(pf, "_resolve_task_title", return_value=""), \
         patch.object(pf, "_update_metrics", return_value=None):
        pf.record_prediction_outcome("t003", success=True, agent_name="test")

    assert fake_alert.get("_counterfactual_regret") is not True


def test_counterfactual_regret_capped_at_0_9():
    """Regret-boosted confidence must not exceed 0.9."""
    from unittest.mock import patch
    from pulse.daemons.neural import prediction_feedback as pf

    fake_alert = {
        "task_id": "t004",
        "status": "active",
        "confidence": 0.1,  # very low → large regret
        "prediction": "barely-there prediction",
    }
    fake_data = {"alerts": [fake_alert]}

    def fake_atomic_update(path, updater, default=None):
        updater(fake_data)

    with patch.object(pf, "atomic_update_json", side_effect=fake_atomic_update), \
         patch.object(pf, "_resolve_task_title", return_value=""), \
         patch.object(pf, "_update_metrics", return_value=None):
        pf.record_prediction_outcome("t004", success=False, agent_name="test")

    assert fake_alert["confidence"] <= 0.9


# ──────────────────────────────────────────────────────────────────
# Item 33: Burst detection — 6+ events in 5s = burst LTP
# ──────────────────────────────────────────────────────────────────
from pulse.brain.plasticity.stdp import _check_burst, _BURST_TIMESTAMPS, _BURST_THRESHOLD, _BURST_WINDOW


def _reset_burst():
    _BURST_TIMESTAMPS.clear()


def test_burst_not_triggered_below_threshold():
    _reset_burst()
    for _ in range(_BURST_THRESHOLD - 1):
        result = _check_burst()
    assert result is False


def test_burst_triggered_at_threshold():
    _reset_burst()
    result = False
    for _ in range(_BURST_THRESHOLD):
        result = _check_burst()
    assert result is True


def test_burst_timestamps_capped_at_10():
    _reset_burst()
    for _ in range(20):
        _check_burst()
    assert len(_BURST_TIMESTAMPS) <= 10


def test_burst_window_constant():
    assert _BURST_WINDOW == 5.0


def test_burst_threshold_constant():
    assert _BURST_THRESHOLD == 6


def test_burst_old_events_excluded():
    """Events older than _BURST_WINDOW should not count."""
    import time
    _reset_burst()
    # Inject old timestamps
    old_time = time.time() - (_BURST_WINDOW + 1)
    for _ in range(10):
        _BURST_TIMESTAMPS.append(old_time)
    # Only 1 new event → should not trigger burst
    result = _check_burst()
    assert result is False


# ──────────────────────────────────────────────────────────────────
# Item 34: DMN proposals auto-posted to task board
# ──────────────────────────────────────────────────────────────────
from pulse.daemons.synthesis.core.dmn_daemon import _dmn_post_ideas


def test_dmn_post_ideas_calls_atomic_update():
    """High-impact ideas with long text should trigger atomic_update_json."""
    from unittest.mock import patch, MagicMock
    from pulse.daemons.synthesis import dmn_daemon as dm

    ideas = [
        {"title": "Test Idea", "idea": "x" * 60, "impact": "High impact statement here"},
    ]
    calls = []

    def fake_atomic(path, updater, default=None):
        board = {}
        updater(board)
        calls.append(board)

    with patch.object(dm, "atomic_update_json" if hasattr(dm, "atomic_update_json") else "pulse.core.brain_utils.atomic_update_json",
                      side_effect=fake_atomic, create=True):
        try:
            _dmn_post_ideas(ideas)
        except Exception:
            pass  # fail-open

    # If atomic_update_json was called, verify task structure
    if calls:
        board = calls[0]
        dispatches = board.get("dispatches", [])
        assert len(dispatches) >= 1
        task = dispatches[0]["tasks"][0]
        assert "EXPLORE:" in task["title"]
        assert task["status"] == "open"


def test_dmn_post_ideas_skips_short_idea():
    """Ideas with short text should not be posted."""
    from unittest.mock import patch
    from pulse.daemons.synthesis import dmn_daemon as dm

    ideas = [{"title": "Short", "idea": "too short", "impact": "something"}]
    calls = []

    def fake_atomic(path, updater, default=None):
        calls.append(True)

    with patch.object(dm, "atomic_update_json", side_effect=fake_atomic, create=True):
        try:
            _dmn_post_ideas(ideas)
        except Exception:
            pass

    # Short idea should not trigger posting
    assert len(calls) == 0


def test_dmn_post_ideas_skips_no_impact():
    """Ideas without impact field should not be posted."""
    from pulse.daemons.synthesis import dmn_daemon as dm
    from unittest.mock import patch

    ideas = [{"title": "No Impact Idea", "idea": "x" * 60}]  # no "impact" key
    calls = []

    def fake_atomic(path, updater, default=None):
        calls.append(True)

    with patch.object(dm, "atomic_update_json", side_effect=fake_atomic, create=True):
        try:
            _dmn_post_ideas(ideas)
        except Exception:
            pass

    assert len(calls) == 0


def test_dmn_post_ideas_fail_open():
    """_dmn_post_ideas must never raise even if internals fail."""
    try:
        _dmn_post_ideas(None)  # bad input
    except Exception as e:
        assert False, f"_dmn_post_ideas raised: {e}"


# ──────────────────────────────────────────────────────────────────
# Item 35: Priority keyword scanner — danger word boost
# ──────────────────────────────────────────────────────────────────
from pulse.brain.brain_query.briefing import format_briefing, _DANGER_WORDS


def test_danger_words_constant_exists():
    assert "security" in _DANGER_WORDS
    assert "crash" in _DANGER_WORDS
    assert "exploit" in _DANGER_WORDS
    assert "injection" in _DANGER_WORDS


def test_danger_boost_applied_on_security_query():
    """Query with 'security' should double confidence of anti_pattern results."""
    hits = {
        "anti_patterns": [{"title": "Never expose secrets", "confidence": 0.4, "type": "anti_pattern", "source": "anti_patterns"}],
        "fails": [{"text": "auth bypass", "confidence": 0.3}],
    }
    results = {"results": hits, "total_results": 2, "elapsed_ms": 5}
    format_briefing("security audit needed", results)
    # After format_briefing, confidence should be boosted
    assert hits["anti_patterns"][0].get("_danger_boost") is True
    assert hits["anti_patterns"][0]["confidence"] >= 0.8  # 0.4 * 2.0 = 0.8


def test_danger_boost_not_applied_on_safe_query():
    """Query without danger words should NOT apply boost."""
    hits = {
        "anti_patterns": [{"title": "Normal advice", "confidence": 0.4, "type": "anti_pattern", "source": "anti_patterns"}],
        "fails": [{"text": "minor issue", "confidence": 0.3}],
    }
    results = {"results": hits, "total_results": 2, "elapsed_ms": 5}
    format_briefing("refactor database query", results)
    assert hits["anti_patterns"][0].get("_danger_boost") is not True
    assert hits["anti_patterns"][0]["confidence"] == 0.4


def test_danger_boost_capped_at_1_0():
    """Boosted confidence should not exceed 1.0."""
    hits = {
        "anti_patterns": [{"title": "High conf", "confidence": 0.7, "type": "anti_pattern", "source": "anti_patterns"}],
        "fails": [],
    }
    results = {"results": hits, "total_results": 1, "elapsed_ms": 5}
    format_briefing("crash vulnerability", results)
    assert hits["anti_patterns"][0]["confidence"] <= 1.0


def test_danger_boost_multiple_words():
    """Query with multiple danger words still works correctly."""
    hits = {
        "anti_patterns": [{"title": "Injection risk", "confidence": 0.4, "type": "anti_pattern", "source": "anti_patterns"}],
        "fails": [{"text": "overflow crash", "confidence": 0.25}],
    }
    results = {"results": hits, "total_results": 2, "elapsed_ms": 5}
    format_briefing("sql injection overflow crash exploit", results)
    assert hits["anti_patterns"][0].get("_danger_boost") is True
    assert hits["fails"][0].get("_danger_boost") is True


def test_format_briefing_fail_open_with_bad_hits():
    """format_briefing danger boost must not raise with empty results."""
    # Danger boost is wrapped in try/except; empty hits dict should not crash
    try:
        format_briefing("security test", {"results": {}, "total_results": 0, "elapsed_ms": 0})
    except Exception as e:
        assert False, f"format_briefing raised with empty hits: {e}"
