"""Adopt attachment. Core owns this record. Cloud and the edge only execute it.

P2HS hosting stays on static generate. Nitro and request-time SSR stay on
customer hosting. ``customer_backend`` stays null unless a legacy caller still
requires that field.
"""

from __future__ import annotations

from typing import Any, Literal
from urllib.parse import urlparse

from pydantic import BaseModel, ConfigDict, Field, model_validator

PLATFORM_API_PREFIX = "/api/_p2hs"
HOSTING_PROFILE = "w4p_static"
PAGE_RUNTIME = "static_generate"
SSR_ON_P2HS = False
DEPLOYMENT_WELL_KNOWN_PATH = "/.well-known/p2hs-deployment"

AttachmentStatus = Literal[
    "detected",
    "awaiting_user_confirmation",
    "awaiting_origin_or_projection",
    "verified",
    "active",
]
BackendSource = Literal["co_located", "separate", "external_runtime_only"]
HostedRuntime = Literal["worker_projection", "external_endpoints"]
WritePolicy = Literal["writable", "read_only", "frozen"]
CustomerAttachment = Literal["edge", "adapter_pr"]
CopyPolicy = Literal["none", "identifiers_only", "declared_fields"]
ProjectionStatus = Literal[
    "projected_and_verified",
    "projected_with_limitations",
    "unavailable_until_external_endpoint",
    "delegated_to_p2hs_cloud",
]
SERVABLE_PROJECTIONS = frozenset({"projected_and_verified", "projected_with_limitations"})


class AdoptionError(ValueError):
    def __init__(self, code: str, message: str) -> None:
        self.code = code
        super().__init__(message)


def greenfield_contract() -> dict[str, Any]:
    """Owned apps stay on Cloud and on the static hosting profile."""
    return {
        "project_mode": "owned",
        "has_external_api": False,
        "hosting_profile": HOSTING_PROFILE,
        "page_runtime": PAGE_RUNTIME,
        "ssr_on_p2hs": SSR_ON_P2HS,
        "customer_backend": None,
    }


def select_project_mode(*, user_requested_migrate: bool, adopt_signal: bool) -> str:
    """Migrate is selected only by an explicit request to move everything onto P2HS.

    A connected backend repo, a verified endpoint, or a writable policy is an
    adopt signal. None of those select migrate.
    """
    if user_requested_migrate:
        return "migrate"
    if adopt_signal:
        return "adopt"
    return "owned"


def claims_reserved(path: str) -> bool:
    value = _normalize_prefix(path.split("?", 1)[0])
    return value == PLATFORM_API_PREFIX or value.startswith(PLATFORM_API_PREFIX + "/")


def strip_platform_prefix(path: str) -> str:
    raw = path.split("?", 1)[0] or "/"
    if raw == PLATFORM_API_PREFIX:
        return "/api"
    if raw.startswith(PLATFORM_API_PREFIX + "/"):
        return "/api" + raw[len(PLATFORM_API_PREFIX):]
    return raw


def _normalize_prefix(prefix: str) -> str:
    value = (prefix or "").strip()
    if len(value) > 1 and value.endswith("/"):
        value = value.rstrip("/")
    return value or "/"


def _reject_prefix(prefix: str, field: str) -> str:
    value = _normalize_prefix(prefix)
    if value == "/":
        raise AdoptionError("prefix_rejected", f"{field} must not be /")
    if claims_reserved(value):
        raise AdoptionError("reserved_prefix", f"{field} must not claim {PLATFORM_API_PREFIX}")
    if not value.startswith("/"):
        raise AdoptionError("prefix_rejected", f"{field} must be an absolute path")
    return value


def _public_origin(origin: str, *, field: str) -> str:
    parsed = urlparse((origin or "").strip())
    if parsed.username or parsed.password:
        raise AdoptionError("origin_has_credentials", f"{field} must not carry credentials")
    if parsed.scheme != "https" or not parsed.hostname:
        raise AdoptionError("origin_rejected", f"{field} must be an https origin")
    return f"https://{parsed.netloc}".rstrip("/")


def _stale_origin(origin: str) -> bool:
    host = (urlparse(origin).hostname or "").lower()
    return host in {"localhost", "127.0.0.1", "0.0.0.0", "::1"} or host.endswith(".local") or host.endswith(".test")


class ExternalEndpoint(BaseModel):
    model_config = ConfigDict(extra="forbid")

    id: str
    role: Literal["api"] = "api"
    browser_prefix: str
    origin: str
    upstream_prefix: str
    production: bool = False
    production_data_confirmed: bool = False
    verified: bool = False

    @model_validator(mode="after")
    def _check(self) -> ExternalEndpoint:
        self.browser_prefix = _reject_prefix(self.browser_prefix, "browser_prefix")
        self.upstream_prefix = _normalize_prefix(self.upstream_prefix)
        if self.upstream_prefix == "/":
            self.upstream_prefix = ""
        self.origin = _public_origin(self.origin, field="origin")
        if self.verified and _stale_origin(self.origin):
            raise AdoptionError("stale_origin", "a verified origin must not be a localhost or dev host")
        if self.production and self.verified and not self.production_data_confirmed:
            raise AdoptionError(
                "production_unconfirmed",
                "P2HS-hosted traffic to a production API needs explicit confirmation",
            )
        return self


class RouteRef(BaseModel):
    model_config = ConfigDict(extra="forbid")

    method: str
    path: str
    endpoint_id: str = ""

    @model_validator(mode="after")
    def _check(self) -> RouteRef:
        self.method = self.method.upper()
        self.path = _reject_prefix(self.path, "path")
        return self


class Projection(BaseModel):
    model_config = ConfigDict(extra="forbid")

    method: str
    path: str
    status: ProjectionStatus
    source: Literal["frontend_repo", "backend_repo"]
    limitations: list[str] = Field(default_factory=list)

    @model_validator(mode="after")
    def _check(self) -> Projection:
        self.method = self.method.upper()
        self.path = _reject_prefix(self.path, "projection path")
        return self


class DataAccess(BaseModel):
    """Default deny. The worker enforces this object and does not widen it."""

    model_config = ConfigDict(extra="forbid")

    methods: list[str] = Field(default_factory=list)
    copy_into_cloud: CopyPolicy = "none"
    declared_fields: list[str] = Field(default_factory=list)
    forward_user_identity: bool = False
    operator_confirmed: bool = False

    @model_validator(mode="after")
    def _check(self) -> DataAccess:
        self.methods = [method.upper() for method in self.methods]
        widened = self.copy_into_cloud != "none" or self.forward_user_identity
        if widened and not self.operator_confirmed:
            raise AdoptionError("data_access_unconfirmed", "widening data access requires operator confirmation")
        return self


class AdoptAttachment(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_mode: Literal["adopt"] = "adopt"
    has_external_api: Literal[True] = True
    attachment_status: AttachmentStatus = "detected"
    backend_source: BackendSource
    backend_write_policy: WritePolicy
    backend_repository: str
    p2hs_hosted_runtime: HostedRuntime
    hosting_profile: Literal["w4p_static"] = HOSTING_PROFILE
    page_runtime: Literal["static_generate"] = PAGE_RUNTIME
    ssr_on_p2hs: Literal[False] = SSR_ON_P2HS
    platform_api_prefix: Literal["/api/_p2hs"] = PLATFORM_API_PREFIX
    external_endpoints: list[ExternalEndpoint] = Field(default_factory=list)
    session_verification: str = "public"
    customer_hosted_attachment: CustomerAttachment = "edge"
    allowed_routes: list[RouteRef] = Field(default_factory=list)
    discovered_routes: list[RouteRef] = Field(default_factory=list)
    projections: list[Projection] = Field(default_factory=list)
    deployment_id: str = ""
    data_access: DataAccess = Field(default_factory=DataAccess)
    legacy_customer_backend_required: bool = False
    legacy_customer_backend: str | None = None

    @property
    def customer_backend(self) -> str | None:
        if self.legacy_customer_backend_required:
            return self.legacy_customer_backend
        return None

    @property
    def routing_active(self) -> bool:
        return self.attachment_status == "active"

    @model_validator(mode="after")
    def _check(self) -> AdoptAttachment:
        repo = (self.backend_repository or "").strip()
        self.backend_repository = repo
        if self.backend_source == "external_runtime_only":
            if self.p2hs_hosted_runtime != "external_endpoints":
                raise AdoptionError("runtime_mismatch", "external_runtime_only uses external_endpoints")
            if self.backend_write_policy != "frozen" or repo != "none":
                raise AdoptionError("runtime_only_frozen", "external_runtime_only is none plus frozen")
        if self.backend_write_policy == "writable" and (not repo or repo == "none"):
            raise AdoptionError("writable_needs_repository", "writable requires a backend repository URL")
        if repo == "none" and self.backend_write_policy != "frozen":
            raise AdoptionError("repository_required", "a missing repository is valid only with frozen")
        if self.customer_hosted_attachment == "adapter_pr" and self.backend_write_policy != "writable":
            raise AdoptionError("adapter_pr_forbidden", "an adapter pull request requires a writable backend")
        self.session_verification = _check_session(self.session_verification)
        self._check_projection_ownership()
        if self.attachment_status == "active":
            self._check_active()
        return self

    def _check_projection_ownership(self) -> None:
        for projection in self.projections:
            if self.backend_source == "co_located" and projection.source != "frontend_repo":
                raise AdoptionError("projection_source", "co_located projections come from the frontend repository")
            if self.backend_source == "separate" and projection.source != "backend_repo":
                raise AdoptionError(
                    "projection_source",
                    "separate projections come from the connected backend repository",
                )
            if self.backend_source == "external_runtime_only":
                raise AdoptionError("projection_source", "external_runtime_only does not project source")

    def _check_active(self) -> None:
        if not self.deployment_id.strip():
            raise AdoptionError("deployment_required", "active attachments bind a deployment id")
        if not self.data_access.operator_confirmed:
            raise AdoptionError("data_access_unconfirmed", "the operator confirms data access before activation")
        if self.p2hs_hosted_runtime == "external_endpoints":
            if not self.external_endpoints or not self.allowed_routes:
                raise AdoptionError("allowlist_required", "endpoint routing needs confirmed endpoints and routes")
            if not self.data_access.methods:
                raise AdoptionError("data_access_unconfirmed", "endpoint routing needs an explicit method list")
            if not all(endpoint.verified for endpoint in self.external_endpoints):
                raise AdoptionError("unverified_endpoint", "every confirmed endpoint is verified before activation")
            known = {endpoint.id for endpoint in self.external_endpoints}
            if any(route.endpoint_id not in known for route in self.allowed_routes):
                raise AdoptionError("unknown_endpoint", "every allowed route names a confirmed endpoint")
            if self.session_verification != "public":
                hosts = {urlparse(endpoint.origin).netloc for endpoint in self.external_endpoints}
                if urlparse(self.session_verification).netloc not in hosts:
                    raise AdoptionError("session_endpoint", "session verification stays on a confirmed endpoint")
        else:
            servable = [item for item in self.projections if item.status in SERVABLE_PROJECTIONS]
            if not servable:
                raise AdoptionError("projection_required", "worker projection needs a verified projection")

    def edge_record(self) -> dict[str, Any]:
        """KV shape. No legacy customer backend and no secret values."""
        record = self.model_dump(mode="json")
        record.pop("legacy_customer_backend_required", None)
        record.pop("legacy_customer_backend", None)
        record["customer_backend"] = None
        return record


def _check_session(value: str) -> str:
    raw = (value or "").strip()
    if raw == "public":
        return raw
    parsed = urlparse(raw)
    if parsed.username or parsed.password:
        raise AdoptionError("origin_has_credentials", "session verification must not carry credentials")
    if parsed.scheme != "https" or not parsed.hostname:
        raise AdoptionError("session_endpoint", "session verification is an https URL or public")
    return raw
