"""P2HS-hosted route split for an adopt attachment.

Owned apps never reach this. Pages stay on the static artifact. ``/api/_p2hs``
is stripped onto the canonical BFF and is never forwarded to an external
endpoint or a projected function.
"""

from __future__ import annotations

from dataclasses import dataclass
from urllib.parse import urlparse

from src.domain.adopt.contract import (
    PLATFORM_API_PREFIX,
    SERVABLE_PROJECTIONS,
    claims_reserved,
    strip_platform_prefix,
)


@dataclass(frozen=True)
class RouteDecision:
    action: str
    path: str
    origin: str = ""
    reason: str = ""


def decide_request(adopt: dict, path: str, method: str = "GET") -> RouteDecision:
    raw = (path or "/").split("?", 1)[0] or "/"
    verb = (method or "GET").upper()
    if claims_reserved(raw):
        return RouteDecision("platform_bff", strip_platform_prefix(raw), reason="platform_prefix")
    if not _api_like(adopt, raw):
        return RouteDecision("artifact", raw, reason="static_generate")
    if adopt.get("attachment_status") != "active":
        return RouteDecision("reject", raw, reason="external_api_unavailable")
    runtime = adopt.get("p2hs_hosted_runtime")
    if runtime == "worker_projection":
        return _project(adopt, raw, verb)
    if runtime == "external_endpoints":
        return _external(adopt, raw, verb)
    return RouteDecision("reject", raw, reason="external_api_unavailable")


def _api_like(adopt: dict, path: str) -> bool:
    if path == "/api" or path.startswith("/api/"):
        return True
    for endpoint in adopt.get("external_endpoints") or []:
        prefix = str(endpoint.get("browser_prefix") or "")
        if prefix and _prefix_match(path, prefix):
            return True
    for route in _known(adopt):
        if _path_match(str(route.get("path") or ""), path):
            return True
    return False


def _known(adopt: dict) -> list[dict]:
    rows: list[dict] = []
    for key in ("allowed_routes", "discovered_routes", "projections"):
        rows.extend(adopt.get(key) or [])
    return rows


def _project(adopt: dict, path: str, method: str) -> RouteDecision:
    for projection in adopt.get("projections") or []:
        if not _path_match(str(projection.get("path") or ""), path):
            continue
        if str(projection.get("method") or "").upper() not in {method, "*"}:
            continue
        status = projection.get("status")
        if status in SERVABLE_PROJECTIONS:
            return RouteDecision("project_worker", path, reason=str(status))
        if status == "delegated_to_p2hs_cloud":
            return RouteDecision("reject", path, reason="delegated_to_p2hs_cloud")
        return RouteDecision("reject", path, reason="unavailable_until_external_endpoint")
    return RouteDecision("reject", path, reason="unavailable_until_external_endpoint")


def _external(adopt: dict, path: str, method: str) -> RouteDecision:
    if claims_reserved(path):
        return RouteDecision("platform_bff", strip_platform_prefix(path), reason="platform_prefix")
    matches = [
        endpoint
        for endpoint in adopt.get("external_endpoints") or []
        if _prefix_match(path, str(endpoint.get("browser_prefix") or ""))
    ]
    if not matches:
        return RouteDecision("reject", path, reason="route_not_allowlisted")
    endpoint = max(matches, key=lambda item: len(str(item.get("browser_prefix") or "")))
    prefix = str(endpoint.get("browser_prefix") or "")
    if prefix == "/" or claims_reserved(prefix):
        return RouteDecision("reject", path, reason="reserved_prefix")
    origin = str(endpoint.get("origin") or "")
    if not origin.startswith("https://") or _stale(origin):
        return RouteDecision("reject", path, reason="origin_rejected")
    if endpoint.get("production") and endpoint.get("production_data_confirmed") is not True:
        return RouteDecision("reject", path, reason="production_unconfirmed")
    allowed = _method_allowed(adopt, endpoint, path, method)
    if not allowed:
        return RouteDecision("reject", path, reason="route_not_allowlisted")
    return RouteDecision("external_api", path, origin=_upstream(endpoint, path), reason="allowlisted")


def _method_allowed(adopt: dict, endpoint: dict, path: str, method: str) -> bool:
    data_methods = {str(item).upper() for item in (adopt.get("data_access") or {}).get("methods") or []}
    if method not in data_methods:
        return False
    for route in adopt.get("allowed_routes") or []:
        if str(route.get("endpoint_id") or "") != str(endpoint.get("id") or ""):
            continue
        if str(route.get("method") or "").upper() not in {method, "*"}:
            continue
        if _path_match(str(route.get("path") or ""), path):
            return True
    return False


def _upstream(endpoint: dict, path: str) -> str:
    prefix = str(endpoint.get("browser_prefix") or "")
    remainder = path[len(prefix):] or "/"
    if not remainder.startswith("/"):
        remainder = "/" + remainder
    upstream = str(endpoint.get("upstream_prefix") or "").rstrip("/")
    if upstream == "/":
        upstream = ""
    origin = str(endpoint.get("origin") or "").rstrip("/")
    if remainder == "/":
        return origin + upstream
    return origin + upstream + remainder


def _prefix_match(path: str, prefix: str) -> bool:
    if not prefix or prefix == "/" or prefix == PLATFORM_API_PREFIX or prefix.startswith(PLATFORM_API_PREFIX + "/"):
        return False
    return path == prefix or path.startswith(prefix + "/")


def _path_match(pattern: str, path: str) -> bool:
    if not pattern or claims_reserved(pattern):
        return False
    if pattern.endswith("*"):
        base = pattern[:-1].rstrip("/")
        return path == base or path.startswith(base + "/")
    left = pattern.split("/")
    right = path.split("/")
    if len(left) != len(right):
        return False
    return all(part.startswith(":") or part == right[index] for index, part in enumerate(left))


def _stale(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")
