"""
Integration tests for P39 neuroscience items 166-170.
Run: python -m pytest tests/test_neuro_p39.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 166: Knowledge gap mapping
# ---------------------------------------------------------------------------
class TestItem166KnowledgeGaps:

    def test_returns_required_keys(self):
        from pulse.brain.metacognition.knowledge_gap_map import get_knowledge_gaps
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = []
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_knowledge_gaps()
        for k in ("gaps", "gap_count", "warning"):
            assert k in result

    def test_sparse_domain_detected(self):
        from pulse.brain.metacognition.knowledge_gap_map import get_knowledge_gaps, _GAP_THRESHOLD
        mock_conn = MagicMock()
        # Return count=1 per table call — 4 tables gives total=4, below threshold=5
        mock_conn.execute.return_value.fetchall.return_value = [("new_domain", 1)]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_knowledge_gaps()
        assert result["gap_count"] >= 1
        gap_domains = [g["domain"] for g in result["gaps"]]
        assert "new_domain" in gap_domains

    def test_rich_domain_not_a_gap(self):
        from pulse.brain.metacognition.knowledge_gap_map import get_knowledge_gaps, _GAP_THRESHOLD
        mock_conn = MagicMock()
        # Only return domains with >= _GAP_THRESHOLD items
        mock_conn.execute.return_value.fetchall.return_value = [
            ("rich_domain", _GAP_THRESHOLD + 10)
        ]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_knowledge_gaps()
        gap_domains = [g["domain"] for g in result["gaps"]]
        assert "rich_domain" not in gap_domains

    def test_warning_contains_domain_names(self):
        from pulse.brain.metacognition.knowledge_gap_map import get_knowledge_gaps
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = [("sparse_domain", 2)]
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_knowledge_gaps()
        if result["gap_count"] > 0:
            assert result["warning"] is not None
            assert "KNOWLEDGE GAPS" in result["warning"]

    def test_exception_fails_open(self):
        from pulse.brain.metacognition.knowledge_gap_map import get_knowledge_gaps
        with patch("pulse.core.brain_db.get_conn", side_effect=Exception("db")):
            result = get_knowledge_gaps()
        assert result["gap_count"] == 0
        assert isinstance(result["gaps"], list)


# ---------------------------------------------------------------------------
# Item 167: Emotional regulation feedback
# ---------------------------------------------------------------------------
class TestItem167EmotionalRegulation:

    def setup_method(self):
        from pulse.core.reward import reward_regulation
        reward_regulation.reset_regulation_count()

    def test_initial_count_zero(self):
        from pulse.core.reward.reward_regulation import get_regulation_count
        assert get_regulation_count() == 0

    def test_increment_works(self):
        from pulse.core.reward.reward_regulation import increment_regulation_count, get_regulation_count
        increment_regulation_count(5)
        assert get_regulation_count() == 5

    def test_below_threshold_no_alert(self):
        from pulse.core.reward.reward_regulation import get_regulation_status
        status = get_regulation_status()
        assert status["over_emotional"] is False
        assert status["warning"] is None

    def test_above_threshold_alerts(self):
        from pulse.core.reward.reward_regulation import (
            increment_regulation_count, get_regulation_status,
            _REGULATION_FIRE_THRESHOLD
        )
        increment_regulation_count(_REGULATION_FIRE_THRESHOLD)
        status = get_regulation_status()
        assert status["over_emotional"] is True
        assert status["warning"] is not None
        assert "OVER-EMOTIONAL" in status["warning"]

    def test_reset_works(self):
        from pulse.core.reward.reward_regulation import (
            increment_regulation_count, reset_regulation_count, get_regulation_count
        )
        increment_regulation_count(100)
        reset_regulation_count()
        assert get_regulation_count() == 0

    def test_returns_required_keys(self):
        from pulse.core.reward.reward_regulation import get_regulation_status
        result = get_regulation_status()
        for k in ("regulation_count", "threshold", "over_emotional", "warning"):
            assert k in result


# ---------------------------------------------------------------------------
# Item 168: Search diversity metric
# ---------------------------------------------------------------------------
class TestItem168SearchDiversity:

    def test_returns_required_keys(self):
        from pulse.brain.brain_search.search_scoring_p36 import compute_search_diversity
        result = compute_search_diversity({})
        for k in ("total_items", "unique_domains", "diversity_score", "domains", "warning"):
            assert k in result

    def test_single_domain_low_diversity(self):
        from pulse.brain.brain_search.search_scoring_p36 import compute_search_diversity
        results = {
            "lessons": [
                {"domain": "brain", "text": "item one"},
                {"domain": "brain", "text": "item two"},
                {"domain": "brain", "text": "item three"},
                {"domain": "brain", "text": "item four"},
                {"domain": "brain", "text": "item five"},
            ]
        }
        result = compute_search_diversity(results)
        assert result["unique_domains"] == 1
        assert result["warning"] is not None
        assert "DIVERSITY" in result["warning"]

    def test_multiple_domains_no_warning(self):
        from pulse.brain.brain_search.search_scoring_p36 import compute_search_diversity
        results = {
            "lessons": [
                {"domain": "brain", "text": "a"},
                {"domain": "security", "text": "b"},
                {"domain": "testing", "text": "c"},
                {"domain": "api", "text": "d"},
                {"domain": "deployment", "text": "e"},
            ]
        }
        result = compute_search_diversity(results)
        assert result["unique_domains"] == 5
        assert result["warning"] is None

    def test_empty_results_zero_items(self):
        from pulse.brain.brain_search.search_scoring_p36 import compute_search_diversity
        result = compute_search_diversity({})
        assert result["total_items"] == 0

    def test_diversity_score_bounded(self):
        from pulse.brain.brain_search.search_scoring_p36 import compute_search_diversity
        results = {
            "lessons": [
                {"domain": f"domain{i}", "text": f"item {i}"} for i in range(20)
            ]
        }
        result = compute_search_diversity(results)
        assert 0.0 <= result["diversity_score"] <= 1.0


# ---------------------------------------------------------------------------
# Item 169: Prediction calibration per domain
# ---------------------------------------------------------------------------
class TestItem169PredictionCalibration:

    def setup_method(self):
        from pulse.brain import prediction_calibration as pc
        pc._DOMAIN_CALIBRATION.clear()

    def test_record_and_retrieve(self):
        from pulse.brain.metacognition.prediction_calibration import (
            record_domain_prediction, get_domain_calibration
        )
        for _ in range(5):
            record_domain_prediction("brain", 0.9, task_succeeded=True)
        result = get_domain_calibration()
        assert "brain" in result["domains"]
        assert result["domains"]["brain"]["count"] == 5

    def test_overconfident_domain_detected(self):
        from pulse.brain.metacognition.prediction_calibration import (
            record_domain_prediction, get_domain_calibration
        )
        # Always predict 0.9 but only 50% succeed
        for i in range(10):
            record_domain_prediction("overconf", 0.9, task_succeeded=(i % 2 == 0))
        result = get_domain_calibration()
        assert "overconf" in result["overconfident_domains"]

    def test_well_calibrated_domain(self):
        from pulse.brain.metacognition.prediction_calibration import (
            record_domain_prediction, get_domain_calibration
        )
        # Predict ~0.6, succeed 60% of time
        for i in range(10):
            record_domain_prediction("calibrated", 0.6, task_succeeded=(i < 6))
        result = get_domain_calibration()
        domain_info = result["domains"].get("calibrated", {})
        if domain_info.get("status") != "insufficient_data":
            assert domain_info["status"] == "well_calibrated"

    def test_insufficient_data_status(self):
        from pulse.brain.metacognition.prediction_calibration import (
            record_domain_prediction, get_domain_calibration
        )
        record_domain_prediction("sparse", 0.8, task_succeeded=True)
        result = get_domain_calibration()
        assert result["domains"]["sparse"]["status"] == "insufficient_data"

    def test_returns_required_keys(self):
        from pulse.brain.metacognition.prediction_calibration import get_domain_calibration
        result = get_domain_calibration()
        for k in ("domains", "overconfident_domains", "underconfident_domains"):
            assert k in result


# ---------------------------------------------------------------------------
# Item 170: Memory integration index
# ---------------------------------------------------------------------------
class TestItem170MemoryIntegrationIndex:

    def test_returns_required_keys(self):
        from pulse.brain.monitoring.memory_integration_index import get_integration_index
        mock_conn = MagicMock()
        mock_conn.execute.return_value.fetchall.return_value = []
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_integration_index()
        for k in ("total_items", "connected_items", "isolated_items",
                  "integration_index", "warning"):
            assert k in result

    def test_well_connected_brain_high_index(self):
        from pulse.brain.monitoring.memory_integration_index import get_integration_index
        mock_conn = MagicMock()
        item_ids = [("id1",), ("id2",), ("id3",)]
        edge_ids = [("id1",), ("id2",), ("id3",)]

        call_count = [0]
        def fake_execute(sql, *args):
            m = MagicMock()
            if "graph_edges" in sql.lower():
                m.fetchall.return_value = edge_ids
            else:
                m.fetchall.return_value = item_ids
            return m

        mock_conn.execute.side_effect = fake_execute
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_integration_index()
        assert result["integration_index"] >= 0.0

    def test_no_edges_zero_connected(self):
        from pulse.brain.monitoring.memory_integration_index import get_integration_index
        mock_conn = MagicMock()

        def fake_execute(sql, *args):
            m = MagicMock()
            if "graph_edges" in sql.lower():
                m.fetchall.return_value = []
            else:
                m.fetchall.return_value = [("id1",), ("id2",), ("id3",)]
            return m

        mock_conn.execute.side_effect = fake_execute
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_integration_index()
        assert result["connected_items"] == 0
        assert result["integration_index"] == 0.0

    def test_exception_fails_open(self):
        from pulse.brain.monitoring.memory_integration_index import get_integration_index
        with patch("pulse.core.brain_db.get_conn", side_effect=Exception("db")):
            result = get_integration_index()
        assert result["integration_index"] == 0.0

    def test_low_integration_warning(self):
        from pulse.brain.monitoring.memory_integration_index import get_integration_index
        mock_conn = MagicMock()

        def fake_execute(sql, *args):
            m = MagicMock()
            if "graph_edges" in sql.lower():
                m.fetchall.return_value = []  # no edges
            else:
                m.fetchall.return_value = [(f"id{i}",) for i in range(25)]  # 25 items
            return m

        mock_conn.execute.side_effect = fake_execute
        with patch("pulse.core.brain_db.get_conn", return_value=mock_conn):
            result = get_integration_index()
        assert result["warning"] is not None
        assert "INTEGRATION" in result["warning"]
