"""Normalized deploy DTOs — portable across Cloudflare Pages/W4P, external origins, etc."""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal, Optional
from uuid import UUID

ArtifactType = Literal["static_site", "edge_worker", "node_server", "shopify_theme"]
HostingProviderId = Literal["cloudflare_pages", "cloudflare_w4p", "external_origin", "stub"]


@dataclass
class RoutingDescriptor:
    """JSON-serialisable routing record stored in W4P KV under ``host:{hostname}``."""

    target_kind: str          # deployment asset kind; W4P is explicit below
    project_ref: str
    project_id: str
    environment: str          # "production" | "preview"
    target_url: str           # CF Pages URL or external origin URL (web artifact)
    bff_origin: Optional[str] = None  # Physical Cloud/BFF origin; KV-only, never Git
    deployment_target_id: Optional[str] = None
    active_deployment_id: Optional[str] = None
    routing_provider_kind: str = "cloudflare_w4p"
    runtime_contract: str = "p2hs-bff-v1"
    # ``legacy`` preserves deployed behavior; ``manifest`` enables the gated
    # contract-driven dispatcher for a certified project.
    routing_policy: str = "legacy"
    # ``cloud_proxy`` keeps /api on the public Cloud gateway. ``project_runtime``
    # sends the whole hostname, including /api, to the project Worker. Missing
    # values are cloud_proxy so existing KV records do not change behavior.
    api_execution: str = "cloud_proxy"
    # Present only for an adopt attachment. Owned routes omit it.
    adopt: Optional[dict[str, Any]] = None

    def as_dict(self) -> dict[str, Any]:
        return {k: v for k, v in self.__dict__.items() if v is not None}


@dataclass
class DeployArtifact:
    """What the hosting layer uploads or routes to.

    Core fields are always ``artifact_type``, ``framework``, and ``source_git_sha``.
    Everything else is optional until your build/CI pipe fills it in.

    **Cloudflare W4P (real adapter):** before the deploy job runs the provider, exactly one
    of these in ``build_metadata`` should be set so the edge Worker serves the real app:

    - ``build_metadata["worker_source"]`` — full Worker module source (string).
    - ``build_metadata["origin_url"]`` — origin URL; the adapter deploys a small proxy Worker.

    If neither is set, deploy still succeeds and routing is idempotent, but HTTP responses
    are a deliberate **maintenance JSON** payload until the artifact pipe is wired.
    """

    artifact_type: ArtifactType
    framework: str
    source_git_sha: str
    entrypoint: str = ""
    asset_manifest: dict[str, Any] = field(default_factory=dict)
    runtime_requirements: dict[str, Any] = field(default_factory=dict)
    env_vars: dict[str, str] = field(default_factory=dict)
    build_metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class DeploymentTarget:
    """Where this deploy should run (control-plane view)."""

    provider: HostingProviderId
    environment: Literal["preview", "production", "staging"]
    tenant_ref: str
    project_ref: str
    hostname: str


@dataclass
class DeploymentOutcome:
    """Result of a hosting provider step."""

    ok: bool
    deploy_url: Optional[str] = None
    provider_resource_id: Optional[str] = None
    provider_version_id: Optional[str] = None
    provider_namespace: Optional[str] = None
    activation_status: Optional[str] = None
    last_completed_step: Optional[str] = None
    message: Optional[str] = None
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class DeployJobContext:
    """Everything the worker needs to call a hosting adapter."""

    deploy_id: UUID
    project_id: UUID
    workspace_id: UUID
    revision_id: UUID
    git_sha: str
    environment: str
    tenant_ref: str
    project_ref: str
    target_hostname: str
    repo_full_name: str
    default_branch: str
    framework: str = ""   # project.type — used by CF Pages build config
