"""GR-05: support.ticket App Block admission (Backend-Complete Capability
Surface Admission pilot).

The support-inbox Feature Pack (support_ticket/support_message entities,
open/pending/resolved/closed workflow, 4 events) already existed with zero
App Block manifest and zero Planner reachability -- see
docs/audits/POST_B2_ARCHITECTURE_RECONCILIATION.md section 3. This admits
exactly one new App Block over that already-built backend: no new Cloud
primitive, no new persistence authority, no new workflow authority.

Full exit review: docs/audits/GR05_EXIT_REVIEW.md
"""

from pathlib import Path

from src.domain.app_blocks.compiler import compile_install_plan
from src.domain.app_blocks.planner import (
    _wants_support_ticket,
    product_intent_from_spec,
    select_capabilities,
)
from src.domain.app_blocks.registry import load_manifests
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))


def test_catalog_admits_exactly_one_new_block():
    # GR-05 took the catalog from 19 -> 20; later gates (e.g. GR-06) admit
    # further blocks, so this checks internal consistency rather than a
    # literal count that would need updating at every future gate.
    from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS

    manifests = load_manifests()
    assert "support.ticket" in {m.id for m in manifests}
    assert len(manifests) == len(CANONICAL_BLOCK_IDS)


def test_support_ticket_phrases_select_the_block():
    for phrase in (
        "we need a support ticket system",
        "build a help desk for our customers",
        "a private helpdesk for it support",
        "our support team needs a ticketing system",
        "staff need a ticket queue for requests",
    ):
        assert _wants_support_ticket(phrase) is True, phrase


def test_negated_support_ticket_language_stays_out():
    assert _wants_support_ticket("does not need a support ticket system") is False
    assert _wants_support_ticket("without a help desk for now") is False


def test_bare_ticket_word_does_not_select_support_ticket():
    """Corpus-witnessed negative control: the pre-existing 'cases' stratum row
    ('...no external ticket integration.') must stay case.management only,
    never support.ticket. Bare 'ticket'/'tickets' is deliberately excluded."""
    text = (
        "case tracking for our internal service desk, with an activity "
        "timeline of record changes. staff only, one organization, no "
        "external ticket integration."
    )
    assert _wants_support_ticket(text) is False


def test_unrelated_ticket_copy_stays_out():
    """Negative control: a concert/event 'ticket' must not select support.ticket."""
    assert _wants_support_ticket("buy a ticket for the concert this weekend") is False


def test_end_to_end_selection_and_install_plan_has_zero_gaps():
    intent = product_intent_from_spec(
        _raw(
            "We need a support ticket system so our staff can track customer "
            "issues from open to resolved."
        )
    )
    predicted = {block for _, block, _ in select_capabilities(intent)}
    assert predicted == {"support.ticket"}

    plan = compile_install_plan(
        _raw(
            "We need a support ticket system so our staff can track customer "
            "issues from open to resolved."
        )
    )
    assert plan.requested_capabilities == ["support_ticket"]
    assert {b.id for b in plan.blocks} == {
        "support.ticket",
        "admin.shell",
        "admin.record_table",
        "admin.record_detail",
    }
    assert plan.gaps == []
    assert plan.required_feature_packs == ["support-inbox"]
    assert set(plan.required_primitives) <= {
        "auth.roles",
        "data.entity",
        "workflow.transition",
    }


def test_assign_tickets_to_selects_both_support_ticket_and_assignment():
    """13A-126 witness: 'staff assign tickets to the right person' should
    select both support.ticket and the pre-existing workflow.assignment
    ('assign tickets to' was already an assignment phrase before GR-05)."""
    intent = product_intent_from_spec(
        _raw(
            "Track support tickets from customers: someone opens an issue, "
            "staff assign tickets to the right person, and each moves "
            "through pending to resolved or closed."
        )
    )
    predicted = {block for _, block, _ in select_capabilities(intent)}
    assert predicted == {"support.ticket", "workflow.assignment"}


def test_full_corpus_is_at_least_130_rows_118_admitted_zero_regression():
    # Was exactly 130/118 at GR-05 close; later gates (e.g. GR-06) append
    # more rows, so this checks GR-05's own floor and overall zero-
    # regression rather than a literal total that would need updating at
    # every future gate.
    from scripts.measure_generation_reliability_13a import corpus, score
    from scripts.measure_planner_selection_13b_b2 import observe_rows

    rows = corpus(ROOT / "scripts/phase_13a_corpus.txt")
    assert len(rows) >= 130
    assert sum(row["oracle_kind"] == "admitted" for row in rows) >= 118
    assert sum(row["oracle_kind"] == "clarify" for row in rows) == 10
    assert sum(row["oracle_kind"] == "admission_required" for row in rows) == 2
    assert next(r for r in rows if r["id"] == "13A-119")["stratum"] == "boundary"
    assert next(r for r in rows if r["id"] == "13A-120")["stratum"] == "boundary"
    support_ids = {f"13A-{n:03d}" for n in range(121, 131)}
    assert {r["stratum"] for r in rows if r["id"] in support_ids} == {"support"}

    observed = observe_rows(rows)
    admitted_total = sum(row["oracle_kind"] == "admitted" for row in rows)
    admitted = [row["observation"] for row in observed if row["oracle_kind"] == "admitted"]
    scored = score(admitted)
    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"]
