"""Phase 10F — quote-request multi-block vertical."""

from __future__ import annotations

import json
import tempfile
from collections import Counter
from pathlib import Path

import pytest

from src.domain.app_blocks.catalogs import default_primitives
from src.domain.app_blocks.execution import assert_install_plan_installable
from src.domain.app_blocks.quote_vertical import (
    QUOTE_BINDING_OWNERS,
    QUOTE_VERTICAL_BLOCKS,
    canonical_quote_workflow,
    compile_quote_vertical_plan,
    generated_quote_routes,
    quote_entity_key,
    quote_transition_allowed,
)
from src.domain.app_blocks.registry import load_default_registry
from src.domain.codegen.backend_guard import validate_backend_repo
from src.domain.codegen.prompt_wrapper import _APP_BLOCK_CONTRACT
from src.domain.feature_packs.events import (
    COMPOSITION_ONLY_EVENT_IDS,
    catalog_event_index,
    resolve_canonical_event_id,
)
from src.domain.feature_packs.normalize import load_pack_contract


def _plan(**kwargs):
    return compile_quote_vertical_plan(**kwargs)


def _by_id(plan, block_id):
    return next(block for block in plan.blocks if block.id == block_id)


def _quote_repo(root: Path, extra: dict[str, str] | None = None) -> None:
    (root / "p2hs").mkdir()
    (root / "pages" / "admin" / "quotes").mkdir(parents=True)
    plan = _plan()
    (root / "p2hs" / "app-blocks-plan.json").write_text(
        json.dumps(
            {
                "adoption": "authoritative",
                "blocks": [{"id": b.id, "version": b.version, "reason": b.reason} for b in plan.blocks],
            }
        ),
        encoding="utf-8",
    )
    (root / "pages" / "request-quote.vue").write_text(
        "<script>submitGeneratedForm({ formKey: 'quote_request', values: { email: 'a@b.c' } })</script>",
        encoding="utf-8",
    )
    (root / "pages" / "admin" / "quotes" / "index.vue").write_text(
        "<script>listRecords('quote_request')</script>",
        encoding="utf-8",
    )
    (root / "pages" / "admin" / "quotes" / "[id].vue").write_text(
        "<script>getRecord('quote_request', id); patchRecord('quote_request', id, { status })</script>",
        encoding="utf-8",
    )
    for rel, source in (extra or {}).items():
        path = root / rel
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(source, encoding="utf-8")


def test_10f_quote_vertical_composes_without_duplicate_block_ids():
    plan = _plan()
    assert_install_plan_installable(plan)
    ids = [block.id for block in plan.blocks]
    assert all(block_id in ids for block_id in QUOTE_VERTICAL_BLOCKS)
    assert ids.count("forms.quote_request") == 1
    assert "forms.contact" not in ids
    counts = Counter(ids)
    assert all(n == 1 for n in counts.values())
    assert plan.required_feature_packs == ["contact-capture"]
    assert plan.required_primitives == sorted(set(plan.required_primitives))
    assert "data.entity" in plan.required_primitives
    assert "workflow.transition" in plan.required_primitives
    assert "form.public_create" in plan.required_primitives
    assert "auth.roles" in plan.required_primitives
    assert plan.required_primitives.count("data.entity") == 1


def test_10f_binding_ownership_is_not_cross_copied():
    plan = _plan()
    by_id = {block.id: set(block.protected_bindings) for block in plan.blocks}
    quote = by_id["forms.quote_request"]
    table = by_id["admin.record_table"]
    detail = by_id["admin.record_detail"]
    shell = by_id["admin.shell"]
    assert QUOTE_BINDING_OWNERS["forms.quote_request"] <= quote
    assert "query" not in quote
    assert "record_read" not in quote
    assert "status_graph" not in table
    assert "status_graph" not in detail
    assert "persistence" not in table
    assert "persistence" not in detail
    assert "admin_auth" in shell
    assert "query" in table
    assert "record_read" in detail


def test_10f_codegen_may_materialize_project_routes():
    plan = _plan()
    routes = generated_quote_routes(plan)
    assert routes["public"] == "/request-quote"
    assert routes["admin_list"] == "/admin/quotes"
    assert routes["admin_detail"] == "/admin/quotes/:id"
    quote = _by_id(plan, "forms.quote_request")
    assert quote.slots["heading"] == "Request a quote"
    table = _by_id(plan, "admin.record_table")
    assert table.slots.get("empty_state") == "No quotes yet"
    assert "table" not in table.slots
    registry = load_default_registry()
    for block_id, host in (
        ("forms.quote_request", "public_form"),
        ("admin.record_table", "table"),
        ("admin.record_detail", "detail"),
        ("admin.shell", "layout"),
    ):
        manifest = registry.require(block_id)
        slot = next(item for item in manifest.slots if item.id == host)
        assert slot.constraints.editable is False


def test_10f_generated_quote_app_uses_platform_bindings():
    with tempfile.TemporaryDirectory() as raw:
        _quote_repo(Path(raw))
        validate_backend_repo(raw)


@pytest.mark.parametrize(
    "rel,source,match",
    [
        ("server/api/quotes.post.ts", "import Database from 'better-sqlite3';", "local SQLite"),
        (
            "server/utils/quotes.ts",
            "import { PrismaClient } from '@prisma/client';",
            "ORM/database client",
        ),
        (
            "server/api/notify.ts",
            "import { Resend } from 'resend';",
            "vendor email client",
        ),
        (
            "pages/request-quote.vue",
            "submitGeneratedForm({ formKey: 'quote_request', values: {} });"
            "const ALLOWED_TRANSITIONS = { new: ['reviewing'], reviewing: ['quoted'] };",
            "project-local workflow",
        ),
        (
            "server/api/events.post.ts",
            "emitDomainEvent('quote_request.submitted')",
            "project-local domain event",
        ),
    ],
)
def test_10f_rejects_private_and_cross_block_substitutes(rel, source, match):
    with tempfile.TemporaryDirectory() as raw:
        extra = {}
        if rel == "pages/request-quote.vue":
            _quote_repo(Path(raw))
            (Path(raw) / rel).write_text(source, encoding="utf-8")
        else:
            extra[rel] = source
            _quote_repo(Path(raw), extra=extra)
        with pytest.raises(ValueError, match="Cloud persistence contract failed") as error:
            validate_backend_repo(raw)
    assert match in str(error.value)


def test_10f_workflow_is_backend_enforced_not_codegen_defined():
    graph = canonical_quote_workflow()
    assert graph["new"] == ["reviewing"]
    assert "lost" in graph["reviewing"]
    assert quote_transition_allowed("reviewing", "quoted") is True
    assert quote_transition_allowed("new", "won") is False
    assert quote_transition_allowed("quoted", "reviewing") is False
    assert quote_transition_allowed("won", "won") is False
    assert "Cloud enforces `workflow.transition`" in _APP_BLOCK_CONTRACT
    assert "Do not generate `/api/quote`" in _APP_BLOCK_CONTRACT


def test_10f_email_missing_is_configuration_required_not_build_blocking():
    primitives = frozenset(p for p in default_primitives() if p != "email.send")
    plan = _plan(primitives=primitives)
    assert_install_plan_installable(plan)
    quote = _by_id(plan, "forms.quote_request")
    email = next(item for item in quote.optional_requirements if item.id == "email.send")
    assert email.status == "unavailable"
    assert all(gap.severity != "build_blocking" for gap in plan.gaps if gap.id == "email.send")
    assert "email.send" in plan.runtime_dependencies


def test_10f_quote_request_does_not_infer_a_domain_event():
    assert "forms.quote_request.submitted" in COMPOSITION_ONLY_EVENT_IDS
    catalog = catalog_event_index([load_pack_contract("contact-capture", {})])
    assert resolve_canonical_event_id("forms.quote_request.submitted", catalog) is None
    assert quote_entity_key() == "quote_request"
    assert "composition-only" in _APP_BLOCK_CONTRACT
