"""Task #159: tracing-gap remediate covers every missing requirement.

Pre-fix:
    `journal:during_work>=1` (and a handful of other tokens) had no entry
    in `_hint_for_missing_key`'s `simple_hints` dict. When the
    requirement was missing, the agent saw the token in `missing[]` but
    the `remediate` string contained NO instruction for how to satisfy
    it. The agent fixed the items with hints, retried, hit the same
    rejection again on the unhinted token, and looped.

Fix:
    Register hints for the previously-unhinted tokens
    (`journal:during_work>=1`, `journal:struggle`, `commits>=1`,
    `pr_open`, `self_verified`). Multi-hint remediate switches to a
    numbered list so the model sees each requirement as a distinct
    instruction instead of a semicolon-blob.
"""

from __future__ import annotations

from unittest.mock import AsyncMock
from uuid import uuid4

import pytest
from roboco.services.gateway.choreographer import Choreographer, ChoreographerDeps

_MIN_HINT_LEN = 10


def _build_choreographer() -> Choreographer:
    deps = ChoreographerDeps(
        task=AsyncMock(),
        work_session=AsyncMock(),
        git=AsyncMock(),
        a2a=AsyncMock(),
        journal=AsyncMock(),
        audit=AsyncMock(),
        evidence_repo=AsyncMock(),
        messaging=AsyncMock(),
    )
    repo = deps.evidence_repo
    for method in (
        "list_unread_a2a",
        "list_unread_mentions",
        "list_pending_notifications",
        "task_metadata_gaps",
        "recent_team_activity",
        "blockers_in_lane",
        "journal_highlights_for_task",
    ):
        getattr(repo, method).return_value = []
    return Choreographer(deps)


# ---------------------------------------------------------------------------
# Hint registration — every previously-unhinted token now has a hint
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "token",
    [
        "journal:during_work>=1",
        "journal:struggle",
        "commits>=1",
        "pr_open",
        "self_verified",
    ],
)
def test_hint_registered_for_previously_unhinted_token(token: str) -> None:
    """Every requirement token must have a non-empty hint so the agent
    knows how to satisfy it."""
    hint = Choreographer._hint_for_missing_key(token, uuid4())
    assert hint is not None, (
        f"requirement token {token!r} has no hint; agent will see the "
        f"token in `missing[]` but no actionable instruction"
    )
    assert len(hint) > _MIN_HINT_LEN, (
        f"hint for {token!r} is suspiciously short: {hint!r}"
    )


def test_during_work_hint_warns_reflect_doesnt_count() -> None:
    """The during_work hint must explicitly say reflect doesn't satisfy
    it — that's the exact confusion smoke-10's be-dev-1 hit."""
    hint = Choreographer._hint_for_missing_key("journal:during_work>=1", uuid4())
    assert hint is not None
    lower = hint.lower()
    assert "reflect" in lower and (
        "does not count" in lower
        or "doesn't count" in lower
        or "do not count" in lower
        or "not count" in lower
    ), f"during_work hint must warn that scope='reflect' doesn't satisfy this: {hint!r}"


# ---------------------------------------------------------------------------
# Multi-hint remediate — numbered list, not semicolon-joined
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_single_missing_remediate_is_plain_string() -> None:
    """One missing item: remediate is the hint itself, no numbering."""
    c = _build_choreographer()
    env = await c._build_tracing_gap(uuid4(), uuid4(), ["journal:reflect"])
    body = env.as_dict()
    assert body["error"] == "tracing_gap"
    assert body["missing"] == ["journal:reflect"]
    assert "Multiple requirements missing" not in (body["remediate"] or "")
    assert "1." not in (body["remediate"] or "")


@pytest.mark.asyncio
async def test_multi_missing_remediate_is_numbered_list() -> None:
    """Multiple missing items: remediate must be a numbered list so the
    agent treats each as a distinct step."""
    c = _build_choreographer()
    env = await c._build_tracing_gap(
        uuid4(),
        uuid4(),
        ["journal:reflect", "journal:during_work>=1", "commits>=1"],
    )
    body = env.as_dict()
    remediate = body["remediate"] or ""
    assert "Multiple requirements missing" in remediate, remediate
    assert "1." in remediate
    assert "2." in remediate
    assert "3." in remediate
    # All three hints must be substring-present (proves each was emitted).
    assert "reflect" in remediate.lower()
    assert "during" in remediate.lower() or "decision" in remediate.lower()
    assert "commit" in remediate.lower()


@pytest.mark.asyncio
async def test_unhinted_token_still_appears_in_remediate() -> None:
    """If a future requirement is added without a hint (defense), the
    agent at least sees the literal token in the remediate — not silently
    dropped."""
    c = _build_choreographer()
    env = await c._build_tracing_gap(
        uuid4(),
        uuid4(),
        ["unknown_future_requirement"],
    )
    body = env.as_dict()
    remediate = body["remediate"] or ""
    assert "unknown_future_requirement" in remediate, (
        f"unhinted token must surface in remediate as fallback. Got: {remediate!r}"
    )


@pytest.mark.asyncio
async def test_acceptance_criteria_grouped_into_one_hint() -> None:
    """Acceptance criteria entries collapse into a single hint regardless
    of how many criteria are missing (so 5 unaddressed criteria don't
    produce 5 separate numbered items)."""
    c = _build_choreographer()
    task_id = uuid4()
    env = await c._build_tracing_gap(
        uuid4(),
        task_id,
        [
            "acceptance_criterion:Branch named correctly",
            "acceptance_criterion:Commit prefix present",
            "acceptance_criterion:PR opened",
        ],
    )
    body = env.as_dict()
    remediate = body["remediate"] or ""
    # All three criterion names should appear in the SAME hint section
    # (collapsed into one hint, not three separate numbered items).
    assert "Branch named correctly" in remediate
    assert "Commit prefix present" in remediate
    assert "PR opened" in remediate
    # Single-hint case → no "Multiple requirements" preamble.
    assert "Multiple requirements missing" not in remediate
