"""Post-B2 residual fix: 13A-034 (case + explicit task/activity language).

Classification: PLANNER_DEFECT. The raw prompt explicitly names both work-item
creation ("create tasks") and activity/history inspection ("inspect activity").
Those are the same tight-phrase style of signal every other rule in
``src/domain/app_blocks/planner.py`` already keys on -- the matcher was simply
missing these two literal phrasings. This is not "case implies task+activity":
see the negative controls below, and the ``mixed``/``cases`` corpus rows where
case-management prompts legitimately do *not* select task.management or
activity.timeline (13A-039, 13A-042 style prompts with no task/activity
language) prove the composition is not implicit.
"""

from pathlib import Path

from src.domain.app_blocks.planner import (
    _wants_activity_timeline,
    _wants_case_tracking,
    _wants_task_management,
    product_intent_from_spec,
    select_capabilities,
)
from src.domain.projects.discovery_schemas import DiscoverySpec, ProductDefinition

ROOT = Path(__file__).resolve().parents[1]


def _raw(prompt: str) -> DiscoverySpec:
    return DiscoverySpec(product=ProductDefinition(purpose=prompt))


PROMPT_13A_034 = (
    "Our staff manage service cases. We need status changes, assign "
    "responsibility, create tasks and inspect activity. Everyone belongs to "
    "one company and must sign in."
)


def test_13a034_selects_all_four_gold_capabilities():
    intent = product_intent_from_spec(_raw(PROMPT_13A_034))
    predicted = {block for _, block, _ in select_capabilities(intent)}
    assert predicted == {
        "case.management",
        "workflow.assignment",
        "task.management",
        "activity.timeline",
    }


def test_create_tasks_phrase_selects_task_management():
    assert _wants_task_management("create tasks and inspect activity") is True


def test_inspect_activity_phrase_selects_activity_timeline():
    assert _wants_activity_timeline("create tasks and inspect activity") is True


def test_negated_create_tasks_does_not_select_task_management():
    assert _wants_task_management("does not need to create tasks here") is False


def test_negated_inspect_activity_does_not_select_activity_timeline():
    assert _wants_activity_timeline("without the ability to inspect activity") is False


def test_case_management_without_task_or_activity_language_stays_narrow():
    """13A-039 style: case + record history, no task language -> no task.management."""
    text = (
        "case tracking for our internal service desk, with an activity "
        "timeline of record changes."
    )
    assert _wants_case_tracking(text) is True
    assert _wants_task_management(text) is False


def test_bare_case_prompt_does_not_imply_task_or_activity():
    """Generalization guard: case archetype alone must not imply task/activity."""
    intent = product_intent_from_spec(
        _raw("Build a case tracking app for our staff. Everyone must sign in.")
    )
    predicted = {block for _, block, _ in select_capabilities(intent)}
    assert "task.management" not in predicted
    assert "activity.timeline" not in predicted


def test_unrelated_activity_copy_stays_out():
    """Negative control: incidental 'activity' prose must not select activity.timeline."""
    text = (
        "we want a public static page about the history of our company and "
        "its business activity. no record history or staff app."
    )
    assert _wants_activity_timeline(text) is False


def test_full_oracle_is_108_of_108_with_zero_precision_regression():
    # GR-05 later appended a 10-row "support" stratum, raising the admitted
    # total 108 -> 118 (see test_planner_support_ticket_gr05.py). This test
    # predates that and is scoped to this file's own 13A-034 fix; asserting
    # against the live admitted count keeps it correct without re-deriving
    # a stale literal.
    from scripts.measure_generation_reliability_13a import corpus
    from scripts.measure_planner_selection_13b_b2 import observe_rows, score_admitted

    rows = corpus(ROOT / "scripts/phase_13a_corpus.txt")
    admitted_total = sum(row["oracle_kind"] == "admitted" for row in rows)
    observed = observe_rows(rows)
    scored = score_admitted(observed)
    assert scored["exact_product_match_rate"]["numerator"] == admitted_total
    assert scored["exact_product_match_rate"]["denominator"] == admitted_total
    assert scored["planner_precision"]["numerator"] == scored["planner_precision"]["denominator"]
    assert scored["planner_recall"]["numerator"] == scored["planner_recall"]["denominator"]

    def stratum(name: str) -> dict:
        admitted = [
            row["observation"]
            for row in observed
            if row["oracle_kind"] == "admitted" and row["stratum"] == name
        ]
        from scripts.measure_generation_reliability_13a import score

        return score(admitted)

    for name in ("crm", "documents", "catalog"):
        stratum_score = stratum(name)
        assert (
            stratum_score["exact_product_match_rate"]["numerator"]
            == stratum_score["exact_product_match_rate"]["denominator"]
        )

    booking = [
        row for row in observed if row["oracle_kind"] == "admitted" and row["stratum"] == "booking"
    ]
    assert all(
        "forms.contact" not in row["observation"]["false_positive"] for row in booking
    )
    boundary = [
        row for row in observed if row["oracle_kind"] == "admitted" and row["stratum"] == "boundary"
    ]
    assert all(
        not ({"forms.contact", "forms.quote_request"} & set(row["observation"]["false_positive"]))
        for row in boundary
    )
