"""Integration tests: verify 4 production wiring points (S2 batch).

Wire S2-1: brain_self_assessment.get_brain_health_report() called from
           consolidation_daemon.run_consolidation() and result written to
           state/brain_health.json.

Wire S2-2: learning_velocity.get_learning_velocity() called from
           brain_self_assessment.get_brain_health_report() and result
           stored in report["learning_velocity"].

Wire S2-3: reconsolidation_stability.record_reconsolidation_event() called
           from reconsolidation.mark_labile().

Wire S2-4: metamemory._decay_domain_expertise() called from
           consolidation_daemon.run_consolidation() after Stage 10.
"""
import inspect
import sys
import time
from pathlib import Path

import pytest

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


# ---------------------------------------------------------------------------
# Wire S2-1: consolidation_daemon calls get_brain_health_report + writes JSON
# ---------------------------------------------------------------------------

class TestWireS2_1_BrainSelfAssessmentWired:
    def test_consolidation_daemon_references_get_brain_health_report(self):
        """run_consolidation source must reference get_brain_health_report."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        assert "get_brain_health_report" in src, (
            "Wire S2-1 missing: run_consolidation() does not call get_brain_health_report"
        )

    def test_consolidation_daemon_imports_brain_self_assessment(self):
        """run_consolidation must import from pulse.brain.brain_self_assessment."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        assert "pulse.brain.brain_self_assessment" in src, (
            "Wire S2-1: wrong import path — expected pulse.brain.brain_self_assessment"
        )

    def test_consolidation_daemon_writes_brain_health_json(self):
        """run_consolidation must write result to brain_health.json in state dir."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        assert "brain_health.json" in src, (
            "Wire S2-1: run_consolidation() does not write brain_health.json"
        )

    def test_consolidation_daemon_call_is_guarded(self):
        """get_brain_health_report call must be wrapped in try/except (fail-open)."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        # Find the try block containing get_brain_health_report
        idx = src.find("get_brain_health_report")
        assert idx != -1, "Wire S2-1: get_brain_health_report not found in source"
        try_idx = src.rfind("try:", 0, idx)
        except_idx = src.find("except", idx)
        assert try_idx != -1 and except_idx != -1, (
            "Wire S2-1: get_brain_health_report call must be wrapped in try/except"
        )

    def test_get_brain_health_report_returns_dict(self):
        """get_brain_health_report() must return a dict with 'timestamp' key."""
        from pulse.brain.monitoring.brain_self_assessment import get_brain_health_report
        report = get_brain_health_report()
        assert isinstance(report, dict), (
            "Wire S2-1: get_brain_health_report() must return dict"
        )
        assert "timestamp" in report, (
            "Wire S2-1: report dict must contain 'timestamp' key"
        )

    def test_get_brain_health_report_has_overall_health(self):
        """get_brain_health_report() result must have 'overall_health' key."""
        from pulse.brain.monitoring.brain_self_assessment import get_brain_health_report
        report = get_brain_health_report()
        assert "overall_health" in report, (
            "Wire S2-1: report dict missing 'overall_health' key"
        )
        assert report["overall_health"] in ("healthy", "caution", "degraded", "unknown"), (
            "Wire S2-1: overall_health must be one of healthy/caution/degraded/unknown"
        )

    def test_get_brain_health_report_has_warnings_list(self):
        """get_brain_health_report() must always include a 'warnings' list."""
        from pulse.brain.monitoring.brain_self_assessment import get_brain_health_report
        report = get_brain_health_report()
        assert "warnings" in report, "Wire S2-1: report missing 'warnings' key"
        assert isinstance(report["warnings"], list), (
            "Wire S2-1: 'warnings' must be a list"
        )


# ---------------------------------------------------------------------------
# Wire S2-2: get_brain_health_report includes learning_velocity
# ---------------------------------------------------------------------------

class TestWireS2_2_LearningVelocityWired:
    def test_brain_self_assessment_references_get_learning_velocity(self):
        """get_brain_health_report source must reference get_learning_velocity."""
        from pulse.brain.monitoring import brain_self_assessment
        src = inspect.getsource(brain_self_assessment.get_brain_health_report)
        assert "get_learning_velocity" in src, (
            "Wire S2-2 missing: get_brain_health_report() does not call get_learning_velocity"
        )

    def test_brain_self_assessment_imports_learning_velocity_module(self):
        """Import path must be pulse.brain.learning_velocity."""
        from pulse.brain.monitoring import brain_self_assessment
        src = inspect.getsource(brain_self_assessment.get_brain_health_report)
        assert "pulse.brain.monitoring.learning_velocity" in src, (
            "Wire S2-2: wrong import path — expected pulse.brain.monitoring.learning_velocity"
        )

    def test_learning_velocity_stored_in_report(self):
        """get_brain_health_report result must contain 'learning_velocity' key."""
        from pulse.brain.monitoring import brain_self_assessment
        src = inspect.getsource(brain_self_assessment.get_brain_health_report)
        assert 'report["learning_velocity"]' in src or "report['learning_velocity']" in src, (
            "Wire S2-2: learning_velocity not stored in report dict"
        )

    def test_learning_velocity_call_is_guarded(self):
        """get_learning_velocity call must be wrapped in try/except (fail-open)."""
        from pulse.brain.monitoring import brain_self_assessment
        src = inspect.getsource(brain_self_assessment.get_brain_health_report)
        idx = src.find("get_learning_velocity")
        assert idx != -1, "Wire S2-2: get_learning_velocity not found in source"
        try_idx = src.rfind("try:", 0, idx)
        except_idx = src.find("except", idx)
        assert try_idx != -1 and except_idx != -1, (
            "Wire S2-2: get_learning_velocity call must be wrapped in try/except"
        )

    def test_report_learning_velocity_key_present(self):
        """Calling get_brain_health_report() must populate 'learning_velocity' in result."""
        from pulse.brain.monitoring.brain_self_assessment import get_brain_health_report
        report = get_brain_health_report()
        # Key must exist (value may be None/dict depending on DB state)
        assert "learning_velocity" in report, (
            "Wire S2-2: 'learning_velocity' key missing from get_brain_health_report() result"
        )

    def test_get_learning_velocity_returns_dict(self):
        """get_learning_velocity() itself must return a dict."""
        from pulse.brain.monitoring.learning_velocity import get_learning_velocity
        result = get_learning_velocity()
        assert isinstance(result, dict), (
            "Wire S2-2: get_learning_velocity() must return dict, got %s" % type(result)
        )

    def test_get_learning_velocity_has_domains_key(self):
        """get_learning_velocity() must have a 'domains' key."""
        from pulse.brain.monitoring.learning_velocity import get_learning_velocity
        result = get_learning_velocity()
        assert "domains" in result, (
            "Wire S2-2: get_learning_velocity() result missing 'domains' key"
        )


# ---------------------------------------------------------------------------
# Wire S2-3: mark_labile calls record_reconsolidation_event
# ---------------------------------------------------------------------------

class TestWireS2_3_ReconsolidationStabilityWired:
    def test_mark_labile_references_record_reconsolidation_event(self):
        """mark_labile source must reference record_reconsolidation_event."""
        from pulse.brain.plasticity import reconsolidation
        src = inspect.getsource(reconsolidation.mark_labile)
        assert "record_reconsolidation_event" in src, (
            "Wire S2-3 missing: mark_labile() does not call record_reconsolidation_event"
        )

    def test_mark_labile_imports_reconsolidation_stability(self):
        """mark_labile must import from pulse.brain.reconsolidation_stability."""
        from pulse.brain.plasticity import reconsolidation
        src = inspect.getsource(reconsolidation.mark_labile)
        assert "pulse.brain.monitoring.reconsolidation_stability" in src, (
            "Wire S2-3: wrong import path — expected pulse.brain.monitoring.reconsolidation_stability"
        )

    def test_mark_labile_event_call_is_guarded(self):
        """record_reconsolidation_event call must be wrapped in try/except (fail-open)."""
        from pulse.brain.plasticity import reconsolidation
        src = inspect.getsource(reconsolidation.mark_labile)
        idx = src.find("record_reconsolidation_event")
        assert idx != -1, "Wire S2-3: record_reconsolidation_event not found in source"
        try_idx = src.rfind("try:", 0, idx)
        except_idx = src.find("except", idx)
        assert try_idx != -1 and except_idx != -1, (
            "Wire S2-3: record_reconsolidation_event call must be wrapped in try/except"
        )

    def test_record_reconsolidation_event_callable(self):
        """record_reconsolidation_event() must be importable and callable."""
        from pulse.brain.monitoring.reconsolidation_stability import record_reconsolidation_event
        assert callable(record_reconsolidation_event), (
            "Wire S2-3: record_reconsolidation_event is not callable"
        )

    def test_record_reconsolidation_event_does_not_raise(self):
        """Calling record_reconsolidation_event() must not raise."""
        from pulse.brain.monitoring.reconsolidation_stability import record_reconsolidation_event
        record_reconsolidation_event()  # must be silent

    def test_mark_labile_triggers_rate_increment(self):
        """Calling mark_labile with a valid table must increment event count."""
        from pulse.brain.monitoring.reconsolidation_stability import (
            record_reconsolidation_event,
            get_reconsolidation_rate,
            _RECON_TIMESTAMPS,
        )
        before = len(_RECON_TIMESTAMPS)
        record_reconsolidation_event()
        after = len(_RECON_TIMESTAMPS)
        assert after >= before, (
            "Wire S2-3: record_reconsolidation_event() did not append to _RECON_TIMESTAMPS"
        )

    def test_get_reconsolidation_rate_returns_dict(self):
        """get_reconsolidation_rate() must return a dict with 'events_last_hour' key."""
        from pulse.brain.monitoring.reconsolidation_stability import get_reconsolidation_rate
        result = get_reconsolidation_rate()
        assert isinstance(result, dict), (
            "Wire S2-3: get_reconsolidation_rate() must return dict"
        )
        assert "events_last_hour" in result, (
            "Wire S2-3: get_reconsolidation_rate() missing 'events_last_hour' key"
        )
        assert "rate_per_hour" in result, (
            "Wire S2-3: get_reconsolidation_rate() missing 'rate_per_hour' key"
        )

    def test_reconsolidation_rate_increments_after_event(self):
        """Rate must reflect events recorded in the last hour."""
        from pulse.brain.monitoring.reconsolidation_stability import (
            record_reconsolidation_event,
            get_reconsolidation_rate,
        )
        record_reconsolidation_event()
        result = get_reconsolidation_rate()
        assert result["events_last_hour"] >= 1, (
            "Wire S2-3: rate did not reflect recently recorded event"
        )


# ---------------------------------------------------------------------------
# Wire S2-4: consolidation_daemon calls _decay_domain_expertise after Stage 10
# ---------------------------------------------------------------------------

class TestWireS2_4_DecayDomainExpertiseWired:
    def test_consolidation_daemon_references_decay_domain_expertise(self):
        """run_consolidation source must reference _decay_domain_expertise."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        assert "_decay_domain_expertise" in src, (
            "Wire S2-4 missing: run_consolidation() does not call _decay_domain_expertise"
        )

    def test_consolidation_daemon_imports_metamemory_for_decay(self):
        """run_consolidation must import _decay_domain_expertise from pulse.brain.metacognition.metamemory."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        assert "pulse.brain.metacognition.metamemory" in src, (
            "Wire S2-4: _decay_domain_expertise not imported from pulse.brain.metacognition.metamemory"
        )

    def test_decay_call_is_guarded(self):
        """_decay_domain_expertise call must be wrapped in try/except (fail-open)."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        idx = src.find("_decay_domain_expertise")
        assert idx != -1, "Wire S2-4: _decay_domain_expertise not found in source"
        try_idx = src.rfind("try:", 0, idx)
        except_idx = src.find("except", idx)
        assert try_idx != -1 and except_idx != -1, (
            "Wire S2-4: _decay_domain_expertise call must be wrapped in try/except"
        )

    def test_decay_called_after_metamemory_stage(self):
        """_decay_domain_expertise must appear after stage_10_metamemory in source."""
        from pulse.daemons.consolidation import consolidation_daemon
        src = inspect.getsource(consolidation_daemon.run_consolidation)
        stage10_idx = src.find("stage_10_metamemory")
        decay_idx = src.find("_decay_domain_expertise")
        assert stage10_idx != -1, "Wire S2-4: stage_10_metamemory not found in source"
        assert decay_idx != -1, "Wire S2-4: _decay_domain_expertise not found in source"
        assert decay_idx > stage10_idx, (
            "Wire S2-4: _decay_domain_expertise must appear after stage_10_metamemory"
        )

    def test_decay_domain_expertise_callable(self):
        """_decay_domain_expertise must be importable and callable."""
        from pulse.brain.metacognition.metamemory import _decay_domain_expertise
        assert callable(_decay_domain_expertise), (
            "Wire S2-4: metamemory._decay_domain_expertise is not callable"
        )

    def test_decay_domain_expertise_does_not_raise(self):
        """Calling _decay_domain_expertise() must not raise."""
        from pulse.brain.metacognition.metamemory import _decay_domain_expertise
        _decay_domain_expertise()  # must be silent (fail-open)
