"""
Integration tests for P35 neuroscience items 146-150.
Run: python -m pytest tests/test_neuro_p35.py -v
"""
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest

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


# ---------------------------------------------------------------------------
# Item 146: Selective attention filter (search_scoring_p34.py)
# ---------------------------------------------------------------------------
class TestItem146SelectiveAttention:

    def test_irrelevant_item_penalised(self):
        from pulse.brain.brain_search.search_scoring_p34 import apply_task_relevance_filter
        results = {
            "wins": [
                {"text": "database migration schema sqlite", "confidence": 0.8},
                {"text": "purple sky elephant flying", "confidence": 0.8},
            ]
        }
        task_desc = "database schema migration sqlite"
        # No patch needed: text_similarity returns 0.0 for fully dissimilar texts
        apply_task_relevance_filter(results, task_desc)
        # Second item has zero word overlap -> should be penalised
        second = results["wins"][1]
        assert second.get("_task_relevance_penalty") is True
        assert second["confidence"] < 0.8

    def test_relevant_item_not_penalised(self):
        from pulse.brain.brain_search.search_scoring_p34 import apply_task_relevance_filter
        results = {
            "wins": [
                {"text": "database schema migration sqlite query", "confidence": 0.8},
            ]
        }
        task_desc = "database schema migration sqlite"
        apply_task_relevance_filter(results, task_desc)
        item = results["wins"][0]
        assert not item.get("_task_relevance_penalty")
        assert item["confidence"] == 0.8

    def test_no_task_description_no_penalty(self):
        from pulse.brain.brain_search.search_scoring_p34 import apply_task_relevance_filter
        results = {"wins": [{"text": "anything", "confidence": 0.7}]}
        apply_task_relevance_filter(results, None)
        assert results["wins"][0]["confidence"] == 0.7

    def test_empty_task_description_no_penalty(self):
        from pulse.brain.brain_search.search_scoring_p34 import apply_task_relevance_filter
        results = {"wins": [{"text": "anything", "confidence": 0.7}]}
        apply_task_relevance_filter(results, "   ")
        assert results["wins"][0]["confidence"] == 0.7

    def test_exception_fails_open(self):
        from pulse.brain.brain_search.search_scoring_p34 import apply_task_relevance_filter
        results = {"wins": [{"text": "x", "confidence": 0.5}]}
        # Should not raise even with completely broken similarity
        apply_task_relevance_filter(results, "task description here")

    def test_task_relevance_score_set(self):
        from pulse.brain.brain_search.search_scoring_p34 import (
            apply_task_relevance_filter, _simple_word_similarity,
        )
        results = {
            "wins": [{"text": "database migration", "confidence": 0.6}]
        }
        apply_task_relevance_filter(results, "database migration schema")
        assert "_task_relevance" in results["wins"][0]
        assert 0.0 <= results["wins"][0]["_task_relevance"] <= 1.0


# ---------------------------------------------------------------------------
# Item 147: Emotional valence tracking per domain (metamemory_p34.py)
# ---------------------------------------------------------------------------
class TestItem147EmotionalValence:

    def setup_method(self):
        # Reset module-level state between tests
        from pulse.brain import metamemory_p34
        metamemory_p34._DOMAIN_VALENCE.clear()

    def test_record_high_valence_text(self):
        from pulse.brain.metacognition.metamemory_p34 import record_domain_valence, get_domain_valence
        record_domain_valence("security", "critical crash fatal danger emergency")
        result = get_domain_valence()
        assert "security" in result
        assert result["security"]["avg_intensity"] > 0

    def test_high_valence_flag_set(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_domain_valence, get_domain_valence, _HIGH_VALENCE_THRESHOLD,
        )
        # Insert enough high-valence text to exceed threshold
        for _ in range(10):
            record_domain_valence("security", "critical crash fatal danger urgent severe")
        result = get_domain_valence()
        assert result["security"]["high_valence"] is True

    def test_normal_text_no_high_valence(self):
        from pulse.brain.metacognition.metamemory_p34 import record_domain_valence, get_domain_valence
        record_domain_valence("docs", "the function returns a value when called")
        result = get_domain_valence()
        if "docs" in result:
            assert result["docs"]["high_valence"] is False

    def test_empty_inputs_no_crash(self):
        from pulse.brain.metacognition.metamemory_p34 import record_domain_valence
        record_domain_valence("", "text")
        record_domain_valence("domain", "")
        record_domain_valence("", "")

    def test_multiple_domains_tracked(self):
        from pulse.brain.metacognition.metamemory_p34 import record_domain_valence, get_domain_valence
        record_domain_valence("a", "critical crash")
        record_domain_valence("b", "success excellent")
        result = get_domain_valence()
        assert "a" in result
        assert "b" in result


# ---------------------------------------------------------------------------
# Item 148: Predictive coding (briefing_ext.py)
# ---------------------------------------------------------------------------
class TestItem148PredictiveCoding:

    def setup_method(self):
        # Clear domain familiarity cache between tests
        from pulse.brain.brain_query import briefing_ext
        briefing_ext._FAMILIARITY_CACHE.clear()

    def test_knowledge_gap_fires(self):
        from pulse.brain.brain_query.briefing_ext import check_predictive_coding
        mock_conn = MagicMock()
        call_n = [0]

        def se(sql, params):
            call_n[0] += 1
            m = MagicMock()
            m.fetchone.return_value = [50]  # 50 items in brain for this domain
            return m

        mock_conn.execute.side_effect = se
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = check_predictive_coding("brain memory query", 5, "brain")
        assert any("KNOWLEDGE GAP" in r for r in result)

    def test_knowledge_surge_fires(self):
        from pulse.brain.brain_query.briefing_ext import check_predictive_coding
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchone.return_value = [10]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = check_predictive_coding("brain memory query", 100, "brain")
        assert any("KNOWLEDGE SURGE" in r for r in result)

    def test_normal_range_no_flag(self):
        from pulse.brain.brain_query.briefing_ext import check_predictive_coding
        mock_conn = MagicMock()
        # DB returns 10 per table, 2 tables queried -> expected = 20
        mock_conn.execute.return_value.fetchone.return_value = [10]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            # expected=20, actual=15 => ratio=0.75 (in normal range 0.5-2.0)
            result = check_predictive_coding("brain memory", 15, "brain")
        assert result == []

    def test_unknown_domain_no_flag(self):
        from pulse.brain.brain_query.briefing_ext import check_predictive_coding
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchone.return_value = [0]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = check_predictive_coding("some query", 5, "unknown_xyz")
        assert result == []

    def test_exception_fails_open(self):
        from pulse.brain.brain_query.briefing_ext import check_predictive_coding
        with patch("pulse.core.brain_db.get_conn", side_effect=Exception("db")):
            result = check_predictive_coding("query", 5, "brain")
        assert isinstance(result, list)


# ---------------------------------------------------------------------------
# Item 149: Agent collaboration history (metamemory_p34.py)
# ---------------------------------------------------------------------------
class TestItem149AgentCollaboration:

    def setup_method(self):
        from pulse.brain import metamemory_p34
        metamemory_p34._COLLAB_HISTORY.clear()

    def test_record_collaboration(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_agent_collaboration, get_collaboration_recommendations,
        )
        record_agent_collaboration("claude", "gemini", "brain", True)
        record_agent_collaboration("claude", "gemini", "brain", True)
        recs = get_collaboration_recommendations()
        assert len(recs) > 0
        rec = recs[0]
        assert set(rec["agents"]) == {"claude", "gemini"}
        assert rec["domain"] == "brain"

    def test_success_rate_computed(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_agent_collaboration, get_collaboration_recommendations,
        )
        record_agent_collaboration("a", "b", "testing", True)
        record_agent_collaboration("a", "b", "testing", True)
        record_agent_collaboration("a", "b", "testing", False)
        recs = get_collaboration_recommendations()
        rec = next(r for r in recs if "a" in r["agents"] and r["domain"] == "testing")
        assert rec["success_rate"] == pytest.approx(2/3, abs=0.01)

    def test_same_agent_pair_no_duplicate_key(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_agent_collaboration, get_collaboration_recommendations,
        )
        record_agent_collaboration("b", "a", "testing", True)
        record_agent_collaboration("a", "b", "testing", True)
        recs = get_collaboration_recommendations()
        # Should merge into single pair entry
        pair_recs = [r for r in recs if set(r["agents"]) == {"a", "b"}]
        assert len(pair_recs) == 1
        assert pair_recs[0]["co_claims"] == 2

    def test_same_agent_rejected(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_agent_collaboration, get_collaboration_recommendations,
        )
        record_agent_collaboration("a", "a", "brain", True)
        recs = get_collaboration_recommendations()
        assert recs == []  # self-collaboration not tracked

    def test_insufficient_claims_not_recommended(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_agent_collaboration, get_collaboration_recommendations,
        )
        record_agent_collaboration("x", "y", "domain", True)
        recs = get_collaboration_recommendations()
        # Only 1 co-claim — need >= 2
        pair_recs = [r for r in recs if set(r["agents"]) == {"x", "y"}]
        assert pair_recs == []

    def test_sorted_by_success_rate_desc(self):
        from pulse.brain.metacognition.metamemory_p34 import (
            record_agent_collaboration, get_collaboration_recommendations,
        )
        record_agent_collaboration("x", "y", "d1", True)
        record_agent_collaboration("x", "y", "d1", True)
        record_agent_collaboration("a", "b", "d2", True)
        record_agent_collaboration("a", "b", "d2", False)
        recs = get_collaboration_recommendations()
        assert len(recs) >= 2
        rates = [r["success_rate"] for r in recs]
        assert rates == sorted(rates, reverse=True)


# ---------------------------------------------------------------------------
# Item 150: Brain entropy metric (metamemory_p34.py)
# ---------------------------------------------------------------------------
class TestItem150BrainEntropy:

    def test_returns_required_keys(self):
        from pulse.brain.metacognition.metamemory_p34 import compute_brain_entropy
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = [
            ("brain", 50), ("testing", 30), ("security", 20)
        ]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = compute_brain_entropy()
        for k in ("entropy", "domain_count", "top_domain", "distribution"):
            assert k in result, f"Missing key: {k}"

    def test_uniform_distribution_max_entropy(self):
        from pulse.brain.metacognition.metamemory_p34 import compute_brain_entropy
        import math
        mock_conn = MagicMock()
        # 4 domains with equal counts -> max entropy = log2(4) = 2.0
        mock_conn.execute.return_value.fetchall.return_value = [
            ("a", 25), ("b", 25), ("c", 25), ("d", 25)
        ]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = compute_brain_entropy()
        assert result["entropy"] == pytest.approx(2.0, abs=0.01)

    def test_single_domain_zero_entropy(self):
        from pulse.brain.metacognition.metamemory_p34 import compute_brain_entropy
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = [("brain", 100)]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = compute_brain_entropy()
        assert result["entropy"] == pytest.approx(0.0, abs=0.001)

    def test_top_domain_identified(self):
        from pulse.brain.metacognition.metamemory_p34 import compute_brain_entropy
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = [
            ("brain", 70), ("testing", 20), ("security", 10)
        ]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = compute_brain_entropy()
        assert result["top_domain"] == "brain"

    def test_exception_returns_zero_entropy(self):
        from pulse.brain.metacognition.metamemory_p34 import compute_brain_entropy
        with patch("pulse.core.brain_db.get_conn", side_effect=Exception("db")):
            result = compute_brain_entropy()
        assert result["entropy"] == 0.0
        assert result["domain_count"] == 0

    def test_distribution_sums_to_one(self):
        from pulse.brain.metacognition.metamemory_p34 import compute_brain_entropy
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = [
            ("a", 40), ("b", 30), ("c", 20), ("d", 10)
        ]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = compute_brain_entropy()
        total = sum(result["distribution"].values())
        assert total == pytest.approx(1.0, abs=0.01)
