#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Integration tests for P32 neuroscience refinements (items 131-135).

Tests use mocks to avoid DB/filesystem dependencies.
Run: python -m pytest tests/test_neuro_p32.py -v
"""
import sys
import types
from unittest.mock import MagicMock

# ---------------------------------------------------------------------------
# Minimal stubs so imports work without full PULSE environment
# ---------------------------------------------------------------------------
def _ensure_stub(name, attrs=None):
    if name not in sys.modules:
        mod = types.ModuleType(name)
        if attrs:
            for k, v in attrs.items():
                setattr(mod, k, v)
        sys.modules[name] = mod
    return sys.modules[name]

_ensure_stub("pulse")
_ensure_stub("pulse.core")
_ensure_stub("pulse.brain")
_ensure_stub("pulse.brain.brain_search")
_ensure_stub("pulse.brain.save_writers")

_bu = _ensure_stub("pulse.core.brain_utils", {
    "load_config": lambda: {},
    "text_similarity": lambda a, b: (
        1.0 if a == b else (
            0.0 if not a or not b else
            len(set(a.lower().split()) & set(b.lower().split())) /
            max(len(set(a.lower().split())), len(set(b.lower().split())), 1)
        )
    ),
})
_ensure_stub("pulse.brain.integrity.provenance", {"get_provenance": lambda fp: {}})

_mock_conn = MagicMock()
_ensure_stub("pulse.core.brain_db", {"get_conn": lambda: _mock_conn})
_ensure_stub("pulse.brain.metacognition.metamemory", {
    "get_metamemory": lambda: {},
    "_get_calibration_factor": lambda: 1.0,
})
_ensure_stub("pulse.core.brain_io", {"_acquire": MagicMock(return_value=MagicMock(
    __enter__=lambda s, *a: s, __exit__=lambda s, *a: False))})

# ---------------------------------------------------------------------------
# Module loaders
# ---------------------------------------------------------------------------
import importlib.util
import os

_BASE = os.path.join(os.path.dirname(__file__), "..")


def _load(rel_path, name):
    spec = importlib.util.spec_from_file_location(
        name, os.path.normpath(os.path.join(_BASE, rel_path))
    )
    mod = importlib.util.module_from_spec(spec)
    sys.modules[name] = mod
    spec.loader.exec_module(mod)
    return mod


def _load_p32():
    return _load(
        "pulse/brain/brain_search/search_scoring_p32.py",
        "pulse.brain.brain_search.search_scoring_p32",
    )


def _load_tracking():
    return _load(
        "pulse/brain/metacognition/metamemory_tracking.py",
        "pulse.brain.metacognition.metamemory_tracking",
    )


# ===========================================================================
# Item 131: Attention blink
# ===========================================================================
class TestAttentionBlink:
    def setup_method(self):
        self.mod = _load_p32()

    def test_blink_reduces_confidence_of_next_two_items(self):
        """High-salience item triggers blink: next 2 items in same source get -30%."""
        items = [
            {"salience": 6.0, "confidence": 0.9},
            {"salience": 1.0, "confidence": 0.8},  # blinked
            {"salience": 1.0, "confidence": 0.7},  # blinked
            {"salience": 1.0, "confidence": 0.6},  # NOT blinked
        ]
        self.mod.apply_attention_blink({"s": items})
        assert items[1].get("_attention_blink") is True
        assert items[1]["confidence"] < 0.8
        assert items[2].get("_attention_blink") is True
        assert items[2]["confidence"] < 0.7
        assert not items[3].get("_attention_blink")
        assert abs(items[3]["confidence"] - 0.6) < 0.001

    def test_blink_penalty_is_30_percent(self):
        """Penalty is exactly 30%."""
        items = [{"salience": 7.0, "confidence": 0.9}, {"salience": 1.0, "confidence": 1.0}]
        self.mod.apply_attention_blink({"s": items})
        assert abs(items[1]["confidence"] - 0.7) < 0.001

    def test_blink_not_triggered_below_threshold(self):
        """Salience <= 5.0 does NOT trigger blink."""
        items = [{"salience": 4.9, "confidence": 0.9}, {"salience": 1.0, "confidence": 0.8}]
        self.mod.apply_attention_blink({"s": items})
        assert not items[1].get("_attention_blink")
        assert abs(items[1]["confidence"] - 0.8) < 0.001

    def test_blink_per_source_isolation(self):
        """Blink is per-source; other sources are unaffected."""
        results = {
            "src_a": [{"salience": 6.0, "confidence": 0.9}, {"salience": 1.0, "confidence": 0.8}],
            "src_b": [{"salience": 1.0, "confidence": 0.5}],
        }
        self.mod.apply_attention_blink(results)
        assert results["src_a"][1].get("_attention_blink") is True
        assert not results["src_b"][0].get("_attention_blink")

    def test_blink_confidence_floor(self):
        """Confidence floor is 0.05."""
        items = [{"salience": 9.0, "confidence": 0.9}, {"salience": 1.0, "confidence": 0.0}]
        self.mod.apply_attention_blink({"s": items})
        assert items[1]["confidence"] >= 0.05

    def test_blink_fail_open(self):
        """Does not raise on bad input."""
        self.mod.apply_attention_blink({"bad": "not_a_list"})
        self.mod.apply_attention_blink({})


# ===========================================================================
# Item 132: Source credibility
# ===========================================================================
class TestSourceCredibility:
    def setup_method(self):
        self.mod = _load_tracking()
        # Reset internal state for isolation
        self.mod._SOURCE_RELIABILITY.clear()

    def test_perfect_source_returns_1(self):
        """Source with all successes returns credibility 1.0."""
        for _ in range(5):
            self.mod._record_source_outcome("perfect", True)
        assert self.mod._get_source_reliability("perfect") == 1.0

    def test_low_reliability_source(self):
        """3 successes / 10 hits = 0.3 credibility."""
        for _ in range(3):
            self.mod._record_source_outcome("bad_src", True)
        for _ in range(7):
            self.mod._record_source_outcome("bad_src", False)
        assert self.mod._get_source_reliability("bad_src") == 0.3

    def test_credibility_applied_to_results(self):
        """apply_source_credibility_to_results reduces confidence for low-credibility source."""
        for _ in range(10):
            self.mod._record_source_outcome("low_src", False)
        results = {"low_src": [{"confidence": 0.8}]}
        self.mod.apply_source_credibility_to_results(results)
        assert results["low_src"][0]["confidence"] < 0.8
        assert "_source_credibility" in results["low_src"][0]

    def test_unknown_source_fail_open(self):
        """Unknown source (< 5 obs) returns 1.0 — fail-open."""
        assert self.mod._get_source_reliability("never_seen_xyz") == 1.0

    def test_perfect_source_no_downgrade(self):
        """Perfect source does not reduce item confidence."""
        for _ in range(10):
            self.mod._record_source_outcome("good_src", True)
        results = {"good_src": [{"confidence": 0.7}]}
        self.mod.apply_source_credibility_to_results(results)
        assert abs(results["good_src"][0]["confidence"] - 0.7) < 0.001


# ===========================================================================
# Item 133: Proactive control
# ===========================================================================
class TestProactiveGoalFilter:
    def setup_method(self):
        self.mod = _load_p32()

    def test_fix_boosts_failures(self):
        """'fix' query boosts fails/anti_patterns."""
        results = {"fails": [{"confidence": 0.5}], "wins": [{"confidence": 0.5}]}
        out = self.mod.apply_proactive_goal_filter(results, "fix bug in auth module")
        assert out["fails"][0]["confidence"] > 0.5
        assert out["fails"][0].get("_proactive_goal_boost") == "fix"

    def test_fix_penalises_off_goal(self):
        """'fix' mildly penalises non-failure sources."""
        results = {"fails": [{"confidence": 0.5}], "wins": [{"confidence": 0.5}]}
        out = self.mod.apply_proactive_goal_filter(results, "fix the timeout")
        assert out["wins"][0]["confidence"] < 0.5

    def test_implement_boosts_wins(self):
        """'implement' boosts wins/patterns."""
        results = {"wins": [{"confidence": 0.5}], "fails": [{"confidence": 0.5}]}
        out = self.mod.apply_proactive_goal_filter(results, "implement new feature")
        assert out["wins"][0]["confidence"] > 0.5

    def test_no_verb_no_change(self):
        """No goal verb = no change."""
        results = {"wins": [{"confidence": 0.5}], "fails": [{"confidence": 0.6}]}
        out = self.mod.apply_proactive_goal_filter(results, "what is the status")
        assert abs(out["wins"][0]["confidence"] - 0.5) < 0.001
        assert abs(out["fails"][0]["confidence"] - 0.6) < 0.001

    def test_fail_open_on_none_query(self):
        """Returns results dict unchanged when query is None."""
        results = {"wins": [{"confidence": 0.5}]}
        out = self.mod.apply_proactive_goal_filter(results, None)
        assert isinstance(out, dict)

    def test_boost_magnitude(self):
        """Boost is exactly +0.15."""
        results = {"fails": [{"confidence": 0.5}]}
        out = self.mod.apply_proactive_goal_filter(results, "fix crash in production")
        expected = min(1.0, 0.5 + 0.15)
        assert abs(out["fails"][0]["confidence"] - expected) < 0.001


# ===========================================================================
# Item 134: Memory competition
# ===========================================================================
class TestMemoryCompetition:
    def setup_method(self):
        self.mod = _load_p32()

    def test_identical_text_penalises_weaker(self):
        """Identical text items: lower confidence one gets 15% penalty."""
        text = "database connection pool timeout error occurs"
        a = {"content": text, "confidence": 0.8}
        b = {"content": text, "confidence": 0.5}
        self.mod.apply_memory_competition({"s": [a, b]})
        assert b.get("_memory_competition") is True
        assert b["confidence"] < 0.5

    def test_penalty_is_15_percent(self):
        """Penalty is exactly 15%."""
        text = "async event loop deadlock prevention strategy"
        a = {"content": text, "confidence": 0.9}
        b = {"content": text, "confidence": 0.6}
        self.mod.apply_memory_competition({"s": [a, b]})
        expected = round(0.6 * 0.85, 4)
        assert abs(b["confidence"] - expected) < 0.001

    def test_stronger_item_not_penalised(self):
        """The higher-confidence item is not penalised."""
        text = "token expiry refresh flow"
        a = {"content": text, "confidence": 0.9}
        b = {"content": text, "confidence": 0.4}
        self.mod.apply_memory_competition({"s": [a, b]})
        assert not a.get("_memory_competition")
        assert abs(a["confidence"] - 0.9) < 0.001

    def test_dissimilar_items_no_penalty(self):
        """Dissimilar items are not penalised."""
        a = {"content": "database index optimization strategy", "confidence": 0.8}
        b = {"content": "machine learning gradient descent", "confidence": 0.5}
        self.mod.apply_memory_competition({"s": [a, b]})
        assert not b.get("_memory_competition")
        assert abs(b["confidence"] - 0.5) < 0.001

    def test_floor_is_005(self):
        """Confidence floor is 0.05."""
        text = "floor test identical repeated text item"
        a = {"content": text, "confidence": 0.9}
        b = {"content": text, "confidence": 0.001}
        self.mod.apply_memory_competition({"s": [a, b]})
        assert b["confidence"] >= 0.05

    def test_single_item_no_competition(self):
        """Single item — no competition possible."""
        results = {"s": [{"content": "solo item", "confidence": 0.8}]}
        self.mod.apply_memory_competition(results)
        assert abs(results["s"][0]["confidence"] - 0.8) < 0.001


# ===========================================================================
# Item 135: Consolidation debt
# ===========================================================================
class TestConsolidationDebt:
    def setup_method(self):
        self.mod = _load_tracking()
        self.mod.reset_consolidation_debt()

    def test_increment_increases_debt(self):
        """increment_consolidation_debt increases counter."""
        self.mod.increment_consolidation_debt(1)
        self.mod.increment_consolidation_debt(1)
        assert self.mod.get_consolidation_debt()["inserted_since_last_run"] == 2

    def test_reset_clears_debt(self):
        """reset_consolidation_debt resets to zero."""
        self.mod.increment_consolidation_debt(50)
        self.mod.reset_consolidation_debt()
        assert self.mod.get_consolidation_debt()["inserted_since_last_run"] == 0

    def test_high_debt_flag_triggers(self):
        """high_debt flag is True when debt > threshold."""
        threshold = self.mod._CONSOLIDATION_DEBT["debt_threshold"]
        self.mod.increment_consolidation_debt(threshold + 1)
        assert self.mod.get_consolidation_debt()["high_debt"] is True

    def test_low_debt_no_flag(self):
        """Low debt: high_debt is False."""
        self.mod.increment_consolidation_debt(5)
        assert self.mod.get_consolidation_debt()["high_debt"] is False

    def test_reset_with_timestamp(self):
        """reset_consolidation_debt records provided timestamp."""
        self.mod.reset_consolidation_debt("2026-03-21T12:00:00+00:00")
        assert self.mod.get_consolidation_debt()["last_consolidation_ts"] == "2026-03-21T12:00:00+00:00"

    def test_bulk_increment(self):
        """increment_consolidation_debt(n) adds n in one call."""
        self.mod.increment_consolidation_debt(100)
        assert self.mod.get_consolidation_debt()["inserted_since_last_run"] == 100


if __name__ == "__main__":
    import pytest
    sys.exit(pytest.main([__file__, "-v"]))
