"""Claim-scoped context briefing — `_briefing_for(full=...)`.

Only context-acquisition verbs (give_me_work / claim / plan / resume / triage)
carry the heavy, verb-invariant sections (company_goals, recent_team_activity,
blockers_in_my_lane, task_handoff, institutional_memory). Every other verb gets
the slim signals-only briefing, and the heavy repo queries are not even issued.
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4

import pytest
from roboco.services.gateway.choreographer import Choreographer
from roboco.services.gateway.evidence_builder import (
    EVIDENCE_DIFF_CAP_CHARS,
    build_evidence_for_task,
    truncate_diff,
)

_PR_NUMBER = 8


def _choreographer_with_repo() -> tuple[Choreographer, AsyncMock]:
    repo = AsyncMock()
    repo.list_unread_a2a.return_value = [{"conversation_id": "c1", "unread": 2}]
    repo.list_unread_mentions.return_value = []
    repo.list_pending_notifications.return_value = [{"notification_id": "n1"}]
    repo.task_metadata_gaps.return_value = []
    repo.recent_team_activity.return_value = [{"task_id": "t1", "status": "pending"}]
    repo.blockers_in_lane.return_value = [{"task_id": "b1"}]
    repo.company_goals.return_value = {"north_star": "win"}
    repo.journal_highlights_for_task.return_value = []
    choreo = object.__new__(Choreographer)
    choreo._deps = MagicMock(evidence_repo=repo)
    return choreo, repo


class TestBriefingScope:
    @pytest.mark.asyncio
    async def test_slim_default_carries_signals_only(self) -> None:
        choreo, repo = _choreographer_with_repo()
        briefing = await choreo._briefing_for(uuid4(), None)
        assert briefing["unread_a2a"] == [{"conversation_id": "c1", "unread": 2}]
        assert briefing["pending_notifications"] == [{"notification_id": "n1"}]
        for heavy in (
            "company_goals",
            "recent_team_activity",
            "blockers_in_my_lane",
            "task_handoff",
            "institutional_memory",
        ):
            assert heavy not in briefing
        # The heavy queries are not even issued on the slim path.
        repo.recent_team_activity.assert_not_awaited()
        repo.blockers_in_lane.assert_not_awaited()
        repo.company_goals.assert_not_awaited()
        repo.journal_highlights_for_task.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_full_carries_heavy_sections(self) -> None:
        choreo, repo = _choreographer_with_repo()
        briefing = await choreo._briefing_for(uuid4(), None, full=True)
        assert briefing["company_goals"] == {"north_star": "win"}
        assert briefing["recent_team_activity"] == [
            {"task_id": "t1", "status": "pending"}
        ]
        assert briefing["blockers_in_my_lane"] == [{"task_id": "b1"}]
        repo.company_goals.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_full_with_task_builds_handoff(self) -> None:
        choreo, repo = _choreographer_with_repo()
        task_id = uuid4()
        task = MagicMock(
            pr_number=_PR_NUMBER,
            pr_url="https://github.com/x/y/pull/8",
            branch_name="feature/backend/abc",
            commits=[{"sha": "abc123", "message": "feat: x"}],
            quick_context=None,
        )
        briefing = await choreo._briefing_for(uuid4(), task_id, task=task, full=True)
        assert briefing["task_handoff"]["pr_number"] == _PR_NUMBER
        repo.journal_highlights_for_task.assert_awaited_once_with(
            task_id, include_ancestors=True
        )

    @pytest.mark.asyncio
    async def test_slim_with_task_omits_handoff(self) -> None:
        choreo, repo = _choreographer_with_repo()
        task = MagicMock(pr_number=8, commits=[{"sha": "abc123"}])
        briefing = await choreo._briefing_for(uuid4(), uuid4(), task=task)
        assert "task_handoff" not in briefing
        repo.journal_highlights_for_task.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_include_company_goals_fetches_only_that_heavy_field(self) -> None:
        """The board_triage idle-branch opt-in: company_goals reaches the
        briefing without pulling in the rest of ``full``'s heavy sections."""
        choreo, repo = _choreographer_with_repo()
        briefing = await choreo._briefing_for(uuid4(), None, include_company_goals=True)
        assert briefing["company_goals"] == {"north_star": "win"}
        repo.company_goals.assert_awaited_once()
        for heavy in (
            "recent_team_activity",
            "blockers_in_my_lane",
            "task_handoff",
            "institutional_memory",
        ):
            assert heavy not in briefing
        # No heavy queries beyond the one company_goals lookup.
        repo.recent_team_activity.assert_not_awaited()
        repo.blockers_in_lane.assert_not_awaited()
        repo.journal_highlights_for_task.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_full_true_ignores_include_company_goals_no_double_fetch(
        self,
    ) -> None:
        """``full=True`` already resolves company_goals via the heavy-sections
        batch; passing include_company_goals=True too must not issue a second
        query."""
        choreo, repo = _choreographer_with_repo()
        briefing = await choreo._briefing_for(
            uuid4(), None, full=True, include_company_goals=True
        )
        assert briefing["company_goals"] == {"north_star": "win"}
        repo.company_goals.assert_awaited_once()


class TestPayloadCaps:
    def test_truncate_diff_caps_and_annotates(self) -> None:
        big = "x" * (EVIDENCE_DIFF_CAP_CHARS + 5_000)
        capped = truncate_diff(big)
        assert capped is not None
        assert len(capped) < len(big)
        assert capped.startswith("x" * 100)  # head preserved
        assert "diff truncated" in capped

    def test_truncate_diff_passes_small_and_none(self) -> None:
        assert truncate_diff("small diff") == "small diff"
        assert truncate_diff(None) is None

    def test_build_evidence_caps_the_diff(self) -> None:
        task = MagicMock(
            pr_number=None,
            pr_url=None,
            commits=[],
            dev_notes=None,
            acceptance_criteria_status=[],
        )
        ev = build_evidence_for_task(
            task,
            journal_highlights=[],
            files_changed=[],
            pr_diff_summary="y" * (EVIDENCE_DIFF_CAP_CHARS * 2),
        )
        assert ev.pr_diff_summary is not None
        assert "diff truncated" in ev.pr_diff_summary
