"""Four non-overlapping runtime surfaces.

``INVOKE_CAPABILITIES`` is only the provider-resolution surface (Class B/C).
Native P2HS engines stay on native ports (Class A). Feature-Pack ``provides``
IDs and connector action slugs are never generated-app runtime addresses.

Invariant: an id belongs to exactly one surface. Packs may *require* a native
port or invoke verb; they do not own that id.
"""
from __future__ import annotations

from typing import Any, Literal

from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS
from src.domain.feature_packs.catalog_v2 import CATALOG_OVERLAYS
from src.domain.feature_packs.contract import CATALOG_PRIMITIVES, RESOLVABLE_PRIMITIVES
from src.integrations.platform_ports import CATALOG, QUEUE_REQUEST_TYPES

Surface = Literal["native", "invoke", "provider_action", "composition"]
Boundary = Literal["A", "B", "C"]

# Cloud job types kept as generated-app aliases of invoke verbs (email pattern).
INVOKE_JOB_ALIASES: dict[str, str] = {
    "email.send": "email.send",
    "payment.checkout.create": "payments.checkout.create",
    "payment.capture": "payments.order.capture",
    "payment.refund": "payments.refund.create",
    "payment.catalog.upsert": "payments.catalog.upsert",
}

PAYMENT_INVOKE_VERBS: frozenset[str] = frozenset(
    canonical for job_type, canonical in INVOKE_JOB_ALIASES.items() if job_type != "email.send"
)

# Scheduler fire is not a public delivery verb. Channel delivery is email.send /
# messaging.sms.send (or in-app notification) at fire time.
NATIVE_INTERNAL_JOBS: frozenset[str] = frozenset({"reminder.send"})

# Transport for invoke verbs; not itself a product capability.
INVOKE_TRANSPORT_JOBS: frozenset[str] = frozenset({"integration.execute"})

# Provider-named jobs that still bypass the invoke catalog. Payments were
# lifted; these remain listed so they cannot be mistaken for Class A.
LEGACY_PROVIDER_JOBS: frozenset[str] = frozenset(
    {
        "dropbox.import",
        "storage.extract",
    }
)

PUBLIC_STUB_REQUEST_TYPES: frozenset[str] = frozenset(
    {
        "pdf.generate",
        "custom.provider.request",
    }
)

MANAGED_STORAGE_PORT_PREFIXES: tuple[str, ...] = ("storage.",)

NATIVE_QUEUE_JOBS: frozenset[str] = frozenset(
    {
        "form.forward",
        "webhook.dispatch",
        "booking.reserve",
        "booking.cancel",
        "inventory.reserve",
        "inventory.release",
        "inventory.commit",
        "record.renew",
        "workforce.shift_assign",
        "workforce.shift_release",
        "reminder.schedule",
    }
)

INVOKE_BOUNDARIES: dict[str, Boundary] = {
    "email.send": "B",
    "messaging.send": "C",
    "messaging.sms.send": "B",
    "automation.workflow.trigger": "C",
    "document.create": "C",
    "document.get": "C",
    "document.insert_text": "C",
    "document.append_text": "C",
    "document.replace_text": "C",
    "spreadsheet.rows.read": "C",
    "spreadsheet.rows.append": "C",
    "spreadsheet.rows.update": "C",
    "storage.file.put": "C",
    "storage.file.get": "C",
    "storage.file.delete": "C",
    "storage.file.exists": "C",
    "storage.file.list": "C",
    "crm.contact.create": "C",
    "crm.contact.get": "C",
    "crm.contact.update": "C",
    "crm.contact.upsert": "C",
    "crm.contact.search": "C",
    "crm.company.create": "C",
    "crm.company.get": "C",
    "crm.company.update": "C",
    "crm.company.search": "C",
    "crm.deal.create": "C",
    "crm.deal.get": "C",
    "crm.deal.update": "C",
    "crm.deal.search": "C",
    "crm.association.create": "C",
    "payments.checkout.create": "C",
    "payments.order.capture": "C",
    "payments.refund.create": "C",
    "payments.catalog.upsert": "C",
}

STORAGE_FILE_SCOPE: dict[str, Any] = {
    "family": "storage.file.*",
    "surface": "invoke",
    "boundary": "C",
    "storage_scope": "customer_connector",
    "platform_fallback": False,
    "managed_storage_surface": "native",
}

DEPRECATED_INVOKE_ALIASES: dict[str, str] = {
    "notification.sms": "messaging.sms.send",
}


def _pack_provide_ids() -> frozenset[str]:
    found: set[str] = set()
    for overlay in CATALOG_OVERLAYS.values():
        for item in overlay.get("provides") or ():
            cap_id = getattr(item, "id", None) or (item.get("id") if isinstance(item, dict) else None)
            if cap_id:
                found.add(str(cap_id))
    return frozenset(found)


def native_port_ids() -> frozenset[str]:
    """Admitted Class A runtime addresses (catalog ports + kernel primitives)."""
    ids: set[str] = set(NATIVE_QUEUE_JOBS)
    ids.update(NATIVE_INTERNAL_JOBS)
    ids.update(RESOLVABLE_PRIMITIVES)
    ids.difference_update(INVOKE_JOB_ALIASES)
    ids.difference_update(INVOKE_BOUNDARIES)
    ids.difference_update(PAYMENT_INVOKE_VERBS)
    for port in CATALOG["ports"]:
        request_type = (port.get("cloud") or {}).get("request_type")
        port_id = str(port["port_id"])
        if request_type in INVOKE_JOB_ALIASES or request_type in INVOKE_TRANSPORT_JOBS:
            continue
        if request_type in LEGACY_PROVIDER_JOBS or request_type in PUBLIC_STUB_REQUEST_TYPES:
            continue
        if request_type in NATIVE_QUEUE_JOBS or request_type in NATIVE_INTERNAL_JOBS:
            ids.add(str(request_type))
        if port_id.startswith("auth.") or port_id.startswith("records.") or port_id.startswith("imports."):
            ids.add(port_id)
        if port_id.startswith(MANAGED_STORAGE_PORT_PREFIXES) and not port_id.startswith("storage.extract"):
            ids.add(port_id)
        if port_id.startswith("bookings.") or port_id.startswith("inventory.") or port_id.startswith("workforce."):
            ids.add(port_id)
        if port_id in {"jobs.get", "jobs.result", "jobs.cancel"}:
            ids.add(port_id)
    ids.add("form.public_create")
    ids.add("data.entity")
    ids.add("auth.roles")
    ids.add("workflow.transition")
    ids.difference_update(invoke_capability_ids())
    ids.difference_update(INVOKE_JOB_ALIASES)
    ids.difference_update(PAYMENT_INVOKE_VERBS)
    return frozenset(ids)


def invoke_capability_ids() -> frozenset[str]:
    from src.domain.connectors.invoke_capabilities import INVOKE_CAPABILITIES

    return frozenset(INVOKE_CAPABILITIES)


def provider_action_slugs() -> frozenset[str]:
    from src.domain.connectors.catalog import CONNECTORS

    return frozenset(action.slug for connector in CONNECTORS.values() for action in connector.actions)


def composition_ids() -> frozenset[str]:
    """Pack/block vocabulary that is not a runtime address."""
    owned = invoke_capability_ids() | native_port_ids() | set(INVOKE_JOB_ALIASES)
    provides = _pack_provide_ids() | CANONICAL_BLOCK_IDS
    return frozenset(provides - owned)


def surface_of(identifier: str) -> Surface | None:
    if identifier in invoke_capability_ids() or identifier in INVOKE_JOB_ALIASES:
        return "invoke"
    if identifier in provider_action_slugs():
        return "provider_action"
    if identifier in native_port_ids():
        return "native"
    if identifier in composition_ids():
        return "composition"
    if identifier in DEPRECATED_INVOKE_ALIASES:
        return "invoke"
    return None


def invoke_spec_metadata(capability_id: str) -> dict[str, Any]:
    boundary = INVOKE_BOUNDARIES.get(capability_id)
    resolution: dict[str, Any]
    if boundary == "B":
        resolution = {"kind": "platform_or_customer", "customer_override": True}
    elif boundary == "C":
        resolution = {"kind": "customer_connection", "required": True}
    else:
        resolution = {"kind": "unknown"}
    meta: dict[str, Any] = {
        "surface": "invoke",
        "boundary": boundary,
        "resolution": resolution,
        "public": {"codegen_default_must_not_be_provider_action": True},
    }
    if capability_id.startswith("storage.file."):
        meta.update(STORAGE_FILE_SCOPE)
    return meta


def catalog_invariants() -> tuple[str, ...]:
    """Executable surface invariants. Failures are regressions."""
    from src.domain.connectors.catalog import CONNECTORS, slugs_implementing
    from src.domain.connectors.invoke_capabilities import INVOKE_CAPABILITIES, capability_surface
    from src.domain.connectors.public_gateway import is_public_bff_proxy_capability

    issues: list[str] = []
    invoke_ids = invoke_capability_ids()
    native_ids = native_port_ids()
    composition = composition_ids()
    actions = provider_action_slugs()

    overlap = (invoke_ids & native_ids) | (invoke_ids & composition) | (native_ids & composition)
    overlap -= set(INVOKE_JOB_ALIASES)
    if overlap:
        issues.append(f"surface overlap: {sorted(overlap)}")

    action_collide = (actions & invoke_ids) | (actions & native_ids) | (actions & composition)
    if action_collide:
        issues.append(f"provider actions collide with other surfaces: {sorted(action_collide)}")

    for stub in PUBLIC_STUB_REQUEST_TYPES:
        if stub in QUEUE_REQUEST_TYPES:
            issues.append(f"{stub}: stub must not be a public catalog queue type")
        if stub in invoke_ids:
            issues.append(f"{stub}: stub must not be an invoke capability")

    for cap_id, spec in INVOKE_CAPABILITIES.items():
        boundary = INVOKE_BOUNDARIES.get(cap_id)
        if cap_id not in INVOKE_BOUNDARIES:
            issues.append(f"{cap_id}: invoke capability missing A/B/C boundary metadata")
        if boundary == "A":
            issues.append(f"{cap_id}: invoke+boundary=A is native; do not put Class A in INVOKE_CAPABILITIES")
        slugs = slugs_implementing(cap_id)
        surface = capability_surface(cap_id, list(slugs))
        if surface["invocable"] and not is_public_bff_proxy_capability(cap_id):
            issues.append(f"{cap_id}: invocable without public BFF allowlist")
        if not surface["invocable"] and is_public_bff_proxy_capability(cap_id):
            issues.append(f"{cap_id}: invocable=false must not have a public capability route")
        if boundary == "C" and spec.get("status") == "implemented" and not slugs:
            issues.append(f"{cap_id}: Class C implemented capability has no adapter implements=")
        if spec.get("status") == "implemented" and not slugs:
            issues.append(f"{cap_id}: implemented without provider implements")

    for connector in CONNECTORS.values():
        for action in connector.actions:
            for capability_id in action.implements:
                if capability_id not in invoke_ids:
                    issues.append(f"{action.slug}: implements unknown invoke capability {capability_id}")
                if capability_id in native_ids:
                    issues.append(f"{action.slug}: implements native port {capability_id}")

    for primitive in CATALOG_PRIMITIVES:
        if primitive in INVOKE_JOB_ALIASES or primitive in invoke_ids:
            issues.append(f"{primitive}: invoke verb must not live in CATALOG_PRIMITIVES")

    return tuple(issues)
