import json
from pathlib import Path

from src.domain.starter_library.catalog import (
    contract_map_payload,
    get_contract,
    known_contract_ids,
    list_contracts,
    merge_contract_map,
)
from src.domain.starter_library.errors import UnknownStarterContractError
from src.domain.starter_library.schemas import (
    STARTER_CATALOG_SCHEMA_VERSION,
    STARTER_CONTRACT_VERSION,
)


def test_starter_contract_version_is_stable():
    assert STARTER_CONTRACT_VERSION == 1
    ids = known_contract_ids()
    assert "form.standard" in ids
    assert "record.table" in ids
    assert "record.create" in ids
    assert "record.edit" in ids
    assert "layout.admin" in ids
    assert get_contract("record.create").layer == "composite"
    assert get_contract("record.edit").layer == "composite"
    assert "adapter.auth" in ids
    assert get_contract("record.table").layer == "composite"
    assert get_contract("ui.button").layer == "primitive"
    assert get_contract("adapter.records").layer == "adapter"
    assert all(item.framework_agnostic for item in list_contracts())
    payload = contract_map_payload()
    assert payload["starter_contract_version"] == 1
    assert payload["catalog_schema_version"] == STARTER_CATALOG_SCHEMA_VERSION
    assert payload["codegen"]["extension_order"][0] == "use"
    assert any(item["id"] == "form.standard" for item in payload["contracts"])
    assert all(item["implementation"] is None for item in payload["contracts"])
    preserved = merge_contract_map(
        {
            "contracts": [
                {"id": "form.standard", "implementation": "components/starter/StandardForm.vue"}
            ]
        },
        payload,
    )
    mapped = {item["id"]: item["implementation"] for item in preserved["contracts"]}
    assert mapped["form.standard"] == "components/starter/StandardForm.vue"
    assert mapped["ui.button"] is None


def test_unknown_starter_contract_raises():
    try:
        get_contract("ContactForm.vue")
        raise AssertionError("expected UnknownStarterContractError")
    except UnknownStarterContractError:
        pass


def test_html_boilerplate_hosts_cover_v1_blocks():
    folder = Path(__file__).resolve().parents[1] / "boilerplates" / "html" / "p2hs"
    hosts = json.loads((folder / "app-block-hosts.json").read_text(encoding="utf-8"))
    keys = {(item["block"], item["slot"]) for item in hosts["hosts"]}
    assert ("forms.contact", "public_form") in keys
    assert ("admin.shell", "layout") in keys
    assert ("crm.lead", "pipeline") in keys
    assert ("crm.customer", "list") in keys
    assert ("crm.customer", "create") in keys
    assert ("crm.pipeline", "pipeline") in keys
    assert ("crm.pipeline", "detail") in keys
    assert ("case.management", "pipeline") in keys
    assert ("case.management", "status") in keys
    assert ("task.management", "status") in keys
    assert ("workflow.assignment", "status") in keys
    assert ("crm.pipeline", "status") in keys
    assert ("documents", "list") in keys
    assert ("documents", "create") in keys
    assert ("documents", "edit") in keys
    assert ("documents", "detail") in keys
    assert ("booking.appointment", "book") in keys
    assert ("booking.appointment", "success") in keys
    assert ("catalog.product", "list") in keys
    assert ("catalog.product", "create") in keys
    assert ("files.attachments", "pipeline") in keys
    assert ("files.attachments", "detail") in keys
    assert ("files.attachments", "empty") in keys
    assert all(item.get("implementation") for item in hosts["hosts"])


HOST_CLUSTER = (
    "layout.page_header",
    "filter.bar",
    "nav.pagination",
    "status.badge",
    "record.list",
    "record.card",
    "search.box",
)
STARTER_MAPS = {
    "html": (
        Path(__file__).resolve().parents[1] / "boilerplates" / "html",
        {
            "layout.page_header": "js/starter-library.js#renderPageHeader",
            "filter.bar": "js/starter-library.js#renderFilterBar",
            "search.box": "js/starter-library.js#renderSearchBox",
            "nav.pagination": "js/starter-library.js#renderPagination",
            "status.badge": "js/starter-library.js#renderStatusBadge",
            "record.list": "js/starter-library.js#renderRecordList",
            "record.card": "js/starter-library.js#renderRecordCard",
        },
        Path("js/starter-library.js"),
    ),
    "nuxt": (
        Path(__file__).resolve().parents[1] / "boilerplates" / "nuxt_app",
        {
            "layout.page_header": "components/starter/PageHeader.vue",
            "filter.bar": "components/starter/FilterBar.vue",
            "search.box": "components/starter/SearchBox.vue",
            "nav.pagination": "components/starter/Pagination.vue",
            "status.badge": "components/starter/StatusBadge.vue",
            "record.list": "components/starter/RecordList.vue",
            "record.card": "components/starter/RecordCard.vue",
        },
        None,
    ),
    "react": (
        Path(__file__).resolve().parents[1] / "boilerplates" / "react_app",
        {
            "layout.page_header": "src/components/starter/library.jsx#PageHeader",
            "filter.bar": "src/components/starter/library.jsx#FilterBar",
            "search.box": "src/components/starter/library.jsx#SearchBox",
            "nav.pagination": "src/components/starter/library.jsx#Pagination",
            "status.badge": "src/components/starter/library.jsx#StatusBadge",
            "record.list": "src/components/starter/RecordList.jsx",
            "record.card": "src/components/starter/library.jsx#RecordCard",
        },
        Path("src/components/starter/library.jsx"),
    ),
}


def test_list_chrome_cluster_is_hosted():
    for contract_id in HOST_CLUSTER:
        assert contract_id in known_contract_ids()
        assert get_contract(contract_id).layer == "composite"
    for folder, expected, shared in STARTER_MAPS.values():
        mapped = {
            item["id"]: item["implementation"]
            for item in json.loads(
                (folder / "p2hs" / "starter-contracts.json").read_text(encoding="utf-8")
            )["contracts"]
        }
        for contract_id, path in expected.items():
            assert mapped[contract_id] == path
            relative = path.split("#", 1)[0]
            assert (folder / relative).is_file()
        if shared:
            source = (folder / shared).read_text(encoding="utf-8")
            if shared.name.endswith(".js"):
                for name in (
                    "renderPageHeader",
                    "renderFilterBar",
                    "renderSearchBox",
                    "renderPagination",
                    "renderStatusBadge",
                    "renderRecordList",
                    "renderRecordCard",
                ):
                    assert f"export function {name}" in source
            else:
                for name in (
                    "PageHeader",
                    "FilterBar",
                    "SearchBox",
                    "Pagination",
                    "StatusBadge",
                    "RecordCard",
                ):
                    assert f"export function {name}" in source
                assert "export function RecordList" in (
                    folder / "src/components/starter/RecordList.jsx"
                ).read_text(encoding="utf-8")


def test_record_list_composes_chrome_hosts():
    contract = get_contract("record.list")
    assert contract.layer == "composite"
    for required in (
        "layout.page_header",
        "filter.bar",
        "record.table",
        "nav.pagination",
        "state.empty",
    ):
        assert required in contract.requires
    assert any("record.table slot" in item for item in contract.avoid_when)
    assert "record.card" in contract.compatible_with


def test_record_card_requires_status_badge():
    contract = get_contract("record.card")
    assert contract.layer == "composite"
    assert "status.badge" in contract.requires
    assert any("record.table" in item for item in contract.avoid_when)


def test_search_box_is_filter_chrome_not_a_search_product():
    contract = get_contract("search.box")
    assert contract.layer == "composite"
    assert "ui.input" in contract.requires
    assert "search.box" in get_contract("filter.bar").requires
    assert any("Search product" in item for item in contract.avoid_when)
