"""Same-origin BFF indirection owned by the W4P routing plane.

Browser clients call ``/api/*`` on the project hostname. The dispatcher reads
``bff_origin`` from the KV routing record and proxies that path. Changing the
physical Cloud origin is a KV write — not a Git commit, frontend edit, or
application rebuild.

When ``bff_origin`` is absent, all traffic stays on the artifact proxy (Pages
Functions remain the BFF). That is the compatibility path for existing projects.
"""

from __future__ import annotations

from dataclasses import dataclass
from urllib.parse import urlparse, urlunparse

from src.domain.adopt.dispatcher import decide_request

API_PREFIX = "/api"
BFF_ROUTE_OWNED_BY = "w4p_runtime"
FRONTEND_BFF_REFERENCE = "/api"
CORS_DEPENDENCY = False


@dataclass(frozen=True)
class Upstream:
    plane: str  # "artifact" | "bff" | "external" | "project_worker" | "reject"
    url: str
    git_commit_required: bool = False
    rebuild_required: bool = False
    frontend_source_changes: int = 0
    reason: str = ""


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


def normalize_origin(origin: str) -> str:
    return (origin or "").strip().rstrip("/")


def resolve_upstream(route: dict, request_url: str, method: str = "GET") -> Upstream:
    """Pick artifact vs BFF from a KV routing record. Pure — no I/O.

    Owned records keep every ``/api`` path on ``bff_origin``. An adopt record
    strips ``/api/_p2hs`` onto that BFF and sends existing APIs elsewhere.
    """
    parsed = urlparse(request_url)
    path = parsed.path or "/"
    suffix = path + (f"?{parsed.query}" if parsed.query else "")
    artifact = normalize_origin(str(route.get("target_url") or ""))
    bff = normalize_origin(str(route.get("bff_origin") or ""))
    adopt = route.get("adopt")
    if isinstance(adopt, dict) and adopt.get("project_mode") == "adopt" and adopt.get("has_external_api") is True:
        decision = decide_request(adopt, path, method)
        if decision.action == "artifact":
            if not artifact:
                raise ValueError("routing record missing target_url")
            return Upstream(plane="artifact", url=artifact + suffix, reason=decision.reason)
        if decision.action == "platform_bff":
            target = decision.path + (f"?{parsed.query}" if parsed.query else "")
            if not bff:
                return Upstream(plane="reject", url="", reason="bff_unconfigured")
            return Upstream(plane="bff", url=bff + target, reason=decision.reason)
        if decision.action == "external_api":
            url = decision.origin + (f"?{parsed.query}" if parsed.query else "")
            return Upstream(plane="external", url=url, reason=decision.reason)
        if decision.action == "project_worker":
            return Upstream(plane="project_worker", url=suffix, reason=decision.reason)
        return Upstream(plane="reject", url="", reason=decision.reason)
    if is_api_path(path) and bff:
        return Upstream(plane="bff", url=bff + suffix)
    if not artifact:
        raise ValueError("routing record missing target_url")
    return Upstream(plane="artifact", url=artifact + suffix)


def failover_bff_origin(route: dict, new_origin: str) -> dict:
    """Return a new KV record with only ``bff_origin`` changed."""
    updated = dict(route)
    updated["bff_origin"] = normalize_origin(new_origin)
    return updated


def tenant_headers(route: dict, hostname: str, public_origin: str) -> dict[str, str]:
    headers = {
        "x-forwarded-host": hostname,
        "x-forwarded-proto": "https",
        "x-p2hs-public-origin": public_origin,
    }
    if route.get("project_id"):
        headers["x-p2hs-project-id"] = str(route["project_id"])
    if route.get("project_ref"):
        headers["x-p2hs-project-ref"] = str(route["project_ref"])
    return headers


def failover_requires_git(before: dict, after: dict) -> bool:
    ignored = {"bff_origin", "adopt"}
    left = {key: value for key, value in before.items() if key not in ignored}
    right = {key: value for key, value in after.items() if key not in ignored}
    return left != right


def public_origin_of(url: str) -> str:
    parsed = urlparse(url)
    if not parsed.scheme or not parsed.netloc:
        return ""
    return urlunparse((parsed.scheme, parsed.netloc, "", "", "", ""))
