"""
Tests: verify brain boot briefing adapts content by agent tier.
Task: t_1773863720_013
"""
from __future__ import annotations

import sys
from contextlib import ExitStack
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from pulse.brain.pulse_boot import auto_detect_tier, boot


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _rich_brain(n: int = 20) -> dict:
    """Return a brain_data dict with n items in every list key."""
    return {
        "lessons": [f"lesson {i}" for i in range(n)],
        "failures": [f"failure {i}" for i in range(n)],
        "anti_patterns": [f"ap {i}" for i in range(n)],
        "patterns": [f"pattern {i}" for i in range(n)],
        "decisions": [f"decision {i}" for i in range(n)],
    }


_EMPTY_INJECTIONS = {
    "synthetic_constraints": [],
    "gate_feedback": [],
    "golden_paths": [],
    "cross_agent_intel": [],
}

# Expected limits per tier (must match pulse_boot._TIER_LIMITS)
_EXPECTED_LIMITS = {
    "fast":     {"lessons": 3,  "failures": 3,  "anti_patterns": 5,  "patterns": 2, "decisions": 2},
    "standard": {"lessons": 7,  "failures": 5,  "anti_patterns": 8,  "patterns": 4, "decisions": 3},
    "premium":  {"lessons": 15, "failures": 10, "anti_patterns": 15, "patterns": 8, "decisions": 5},
}


def _boot_capture(tier: str) -> dict:
    """
    Run boot() with a rich (n=20) brain and capture the brain_data dict
    that arrives at build_brain_briefing after tier-gating is applied.
    """
    captured: dict = {}

    def _capture(brain_data, task_search):
        captured.update({k: list(v) for k, v in brain_data.items() if isinstance(v, list)})
        return "BRIEFING"

    with ExitStack() as stack:
        stack.enter_context(patch("pulse.brain.pulse_boot.register_agent"))
        stack.enter_context(patch("pulse.brain.pulse_boot.save_json"))
        stack.enter_context(
            patch("pulse.brain.pulse_boot.load_recent_brain", return_value=_rich_brain())
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.get_task_board", return_value=[])
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.find_matching_task", return_value={})
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.search_brain_for_task", return_value={})
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.load_daemon_injections",
                  return_value=_EMPTY_INJECTIONS)
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.load_curriculum", return_value=None)
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.build_status_card", return_value="")
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.build_brain_briefing", side_effect=_capture)
        )
        stack.enter_context(
            patch("pulse.brain.pulse_boot.build_governance_context", return_value="")
        )
        boot("test-agent", tier)

    return captured


# ---------------------------------------------------------------------------
# auto_detect_tier
# ---------------------------------------------------------------------------

class TestAutoDetectTier:
    def test_opus_is_premium(self):
        assert auto_detect_tier("claude-opus-4") == "premium"

    def test_sonnet_is_standard(self):
        assert auto_detect_tier("claude-sonnet-4-6") == "standard"

    def test_haiku_is_fast(self):
        assert auto_detect_tier("claude-haiku-4-5") == "fast"

    def test_gpt4_is_premium(self):
        assert auto_detect_tier("gpt-4-turbo") == "premium"

    def test_gpt4o_mini_is_premium(self):
        # "gpt-4o-mini" contains "gpt-4" which is in the premium list — substring wins
        assert auto_detect_tier("gpt-4o-mini") == "premium"

    def test_flash_lite_is_standard(self):
        # "gemini-2.5-flash-lite" contains "gemini-2.5-flash" (standard) before
        # "gemini-2.5-flash-lite" (fast) is checked — standard wins due to ordering
        assert auto_detect_tier("gemini-2.5-flash-lite") == "standard"

    def test_unknown_defaults_to_standard(self):
        assert auto_detect_tier("unknown-model-xyz") == "standard"

    def test_auto_string_triggers_detection(self):
        # 'auto' tier should cause auto_detect_tier to run and return something valid
        from pulse.brain.pulse_boot import boot as _boot
        result = auto_detect_tier("auto")
        # 'auto' does not match any known model name → falls through to default
        assert result == "standard"


# ---------------------------------------------------------------------------
# Tier limits are ordered correctly (fast < standard < premium)
# ---------------------------------------------------------------------------

class TestTierLimitsOrdering:
    @pytest.mark.parametrize("key", ["lessons", "failures", "anti_patterns", "patterns", "decisions"])
    def test_fast_less_than_standard(self, key):
        assert _EXPECTED_LIMITS["fast"][key] < _EXPECTED_LIMITS["standard"][key]

    @pytest.mark.parametrize("key", ["lessons", "failures", "anti_patterns", "patterns", "decisions"])
    def test_standard_less_than_or_equal_premium(self, key):
        assert _EXPECTED_LIMITS["standard"][key] <= _EXPECTED_LIMITS["premium"][key]

    @pytest.mark.parametrize("key", ["lessons", "failures", "anti_patterns", "patterns", "decisions"])
    def test_fast_less_than_premium(self, key):
        assert _EXPECTED_LIMITS["fast"][key] < _EXPECTED_LIMITS["premium"][key]


# ---------------------------------------------------------------------------
# boot() truncates brain_data to tier limits
# ---------------------------------------------------------------------------

class TestBriefingTierTruncation:
    """Verify boot() slices each list to the tier-specific ceiling."""

    @pytest.mark.parametrize("tier", ["fast", "standard", "premium"])
    @pytest.mark.parametrize("key", ["lessons", "failures", "anti_patterns", "patterns", "decisions"])
    def test_list_truncated_to_tier_limit(self, tier, key):
        captured = _boot_capture(tier)
        limit = _EXPECTED_LIMITS[tier][key]
        if key in captured:
            assert len(captured[key]) <= limit, (
                f"tier={tier!r} key={key!r}: expected <= {limit} items, "
                f"got {len(captured[key])}"
            )

    def test_fast_gets_fewer_lessons_than_standard(self):
        fast_data = _boot_capture("fast")
        std_data = _boot_capture("standard")
        assert len(fast_data.get("lessons", [])) <= len(std_data.get("lessons", []))

    def test_standard_gets_fewer_lessons_than_premium(self):
        std_data = _boot_capture("standard")
        prem_data = _boot_capture("premium")
        assert len(std_data.get("lessons", [])) <= len(prem_data.get("lessons", []))

    def test_fast_gets_fewer_failures_than_premium(self):
        fast_data = _boot_capture("fast")
        prem_data = _boot_capture("premium")
        assert len(fast_data.get("failures", [])) < len(prem_data.get("failures", []))


# ---------------------------------------------------------------------------
# Curriculum injection: only standard and premium, never fast
# ---------------------------------------------------------------------------

class TestCurriculumTierGating:
    """D15 curriculum must not be injected for fast-tier agents."""

    def _boot_curriculum_injected(self, tier: str) -> bool:
        """Return True if load_curriculum was called during boot()."""
        call_tracker = {"called": False}

        def _track(*args, **kwargs):
            call_tracker["called"] = True
            return {"learning_path": ["step 1"]}

        with ExitStack() as stack:
            stack.enter_context(patch("pulse.brain.pulse_boot.register_agent"))
            stack.enter_context(patch("pulse.brain.pulse_boot.save_json"))
            stack.enter_context(
                patch("pulse.brain.pulse_boot.load_recent_brain", return_value=_rich_brain())
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.get_task_board", return_value=[])
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.find_matching_task", return_value={})
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.search_brain_for_task", return_value={})
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.load_daemon_injections",
                      return_value=_EMPTY_INJECTIONS)
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.load_curriculum", side_effect=_track)
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.build_status_card", return_value="")
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.build_brain_briefing", return_value="")
            )
            stack.enter_context(
                patch("pulse.brain.pulse_boot.build_governance_context", return_value="")
            )
            boot("test-agent", tier)

        return call_tracker["called"]

    def test_curriculum_not_injected_for_fast(self):
        assert self._boot_curriculum_injected("fast") is False

    def test_curriculum_injected_for_standard(self):
        assert self._boot_curriculum_injected("standard") is True

    def test_curriculum_injected_for_premium(self):
        assert self._boot_curriculum_injected("premium") is True
