"""Wave 3: order_line as related records on order-management.

Header amount stays an explicit parent field. No new Cloud jobs.
checkout_app_block remains a coverage gap.
"""
from __future__ import annotations

import json
import re
from pathlib import Path

import pytest
import yaml
from src.domain.app_blocks.generated_product_conformance_12h import (
    ENTITY_BY_BLOCK,
    KernelDeniedError,
    PlatformKernel,
)
from src.domain.app_blocks.hosts import merge_host_map
from src.domain.app_blocks.registry import load_default_registry
from src.domain.feature_packs.catalog_v2 import overlay_for
from src.integrations.platform_ports import QUEUE_REQUEST_TYPES

from tests.test_phase11_pack_migration_materialization import (
    _captured_composition,
    _load_migration,
)

ROOT = Path(__file__).resolve().parents[1]
REGISTRY = ROOT / "src/domain/app_blocks/platform_coverage_registry.v1.yaml"
WAVE3 = "alembic/versions/159_order_line_child_records.py"
CHILD_SLOTS = ("lines", "line_create", "line_detail")


def _live() -> dict:
    return _load_migration(WAVE3).order_lifecycle_v1_1()


def _entity(composition: dict, key: str) -> dict:
    return next(item for item in composition["entities"] if item["entity_key"] == key)


def _keys(entity: dict) -> set[str]:
    return {field["key"] for field in entity["schema"]["fields"]}


def _host_fragment(root: Path, implementation: str) -> str:
    rel, _, symbol = implementation.partition("#")
    text = (root / rel).read_text(encoding="utf-8")
    if not symbol:
        return text
    match = re.search(
        rf"(?:export\s+)?(?:async\s+)?function\s+{re.escape(symbol)}\b",
        text,
    )
    if not match:
        return text
    rest = text[match.end() :]
    nxt = re.search(r"\nexport\s+(?:async\s+)?function\s+", rest)
    end = match.end() + nxt.start() if nxt else len(text)
    return text[match.start() : end]


def _schema_dir(framework: str) -> Path:
    return {
        "html": Path("js/entity-schemas"),
        "nuxt_app": Path("lib/entity-schemas"),
        "react_app": Path("src/lib/entity-schemas"),
    }[framework]


def test_historical_order_pack_keeps_parent_only_schema():
    composition = _captured_composition(
        "alembic/versions/146_order_management_pack.py", "order-management"
    )
    assert [item["entity_key"] for item in composition["entities"]] == ["order_record"]
    assert "order_line" not in {item["entity_key"] for item in composition["entities"]}
    assert "amount" in _keys(composition["entities"][0])


def test_live_wave3_composition_adds_order_line_without_jobs():
    composition = _live()
    assert [item["entity_key"] for item in composition["entities"]] == [
        "order_record",
        "order_line",
    ]
    assert composition["function_slugs"] == []
    assert composition["connector_actions"] == []
    parent = _entity(composition, "order_record")
    assert "amount" in _keys(parent)
    line = _entity(composition, "order_line")
    rel = next(
        field for field in line["schema"]["fields"] if field["key"] == "order_record_id"
    )
    assert rel["type"] == "relation"
    assert rel["relation_entity_key"] == "order_record"
    assert rel["indexed"] is True
    assert _keys(line) == {
        "order_record_id",
        "description",
        "quantity",
        "unit_price",
        "notes",
    }


def test_overlay_declares_order_line_on_existing_pack():
    keys = {entity.entity_key for entity in overlay_for("order-management")["entities"]}
    assert keys == {"order_record", "order_line"}
    assert overlay_for("order-management")["entities"][0].entity_key == "order_record"


def test_app_block_slots_are_record_contracts_not_job_ports():
    registry = load_default_registry()
    block = registry.require("orders.order")
    by_id = {slot.id: slot for slot in block.slots}
    for slot_id in CHILD_SLOTS:
        slot = by_id[slot_id]
        assert slot.type == "ui_slot"
        assert slot.query_source is None
        assert slot.component_contract in {
            "record.table",
            "record.create",
            "record.detail",
        }
    requires = registry.canonical.require("orders.order").requires
    primitives = {dep.id for dep in requires if dep.kind == "primitive"}
    assert primitives == {"data.entity"}
    assert ENTITY_BY_BLOCK["orders.order"] == "order_record"


def test_checkout_app_block_gap_stays():
    registry = yaml.safe_load(REGISTRY.read_text(encoding="utf-8"))
    by_id = {row["id"]: row for row in registry["capabilities"]}
    assert "checkout_app_block" in by_id["orders"]["gaps"]
    assert by_id["orders.order"]["gaps"] == ["checkout_app_block"]


def test_no_new_cloud_jobs_for_order_lines():
    from src.domain.cloud.service import _REQUEST_TYPE_POLICY

    forbidden = {
        "order.line",
        "order.line.add",
        "orders.line",
        "order_line.create",
    }
    assert not (forbidden & set(QUEUE_REQUEST_TYPES))
    assert not (forbidden & set(_REQUEST_TYPE_POLICY))


def test_header_amount_stays_explicit_after_child_writes():
    kernel = PlatformKernel(namespace="wave3-order-line")
    kernel.as_anonymous()
    with pytest.raises(KernelDeniedError):
        kernel.create_record("order_line", {"description": "Labor"})
    kernel.as_staff()
    order = kernel.create_record(
        "order_record",
        {
            "reference": "WO-1",
            "status": "open",
            "amount": 120,
            "currency": "USD",
        },
    )
    kernel.create_record(
        "order_line",
        {
            "order_record_id": order["id"],
            "description": "Labor",
            "quantity": 2,
            "unit_price": 40,
        },
    )
    kernel.create_record(
        "order_line",
        {
            "order_record_id": order["id"],
            "description": "Parts",
            "quantity": 1,
            "unit_price": 15,
        },
    )
    assert kernel.get_record("order_record", order["id"])["amount"] == 120
    assert len(kernel.list_records("order_line")) == 2


def test_materialized_order_line_hosts_use_relative_records_filter():
    for framework in ("html", "nuxt_app", "react_app"):
        root = ROOT / "boilerplates" / framework
        hosts = merge_host_map(json.loads((root / "p2hs/app-block-hosts.json").read_text()))
        rows = {item["slot"]: item for item in hosts["hosts"] if item["block"] == "orders.order"}
        for slot_id in CHILD_SLOTS:
            source = _host_fragment(root, rows[slot_id]["implementation"])
            assert "order_line" in source
            assert "/api/jobs/" not in source
            assert "/v1/projects/" not in source
            assert "cloudgate" not in source
            assert "INTERNAL_CONTROL_TOKEN" not in source
            assert "createCheckout" not in source
            assert "/api/checkout" not in source
            if slot_id == "lines":
                assert "filter" in source
                assert "order_record_id" in source
                assert "parentRecordId" in source
            if slot_id == "line_create":
                assert "order_record_id" in source
                assert "parentRecordId" in source
        schema = json.loads(
            (root / _schema_dir(framework) / "order_line.json").read_text(encoding="utf-8")
        )
        keys = {field["key"] for field in schema["fields"]}
        assert "order_record_id" in keys
