"""One Worker entrypoint for the Python uploader and GitHub Actions.

The generated Worker is the versioned P2HS Generated App Base Runtime plus the
customer's application routes. This module wraps that runtime with the
Cloudflare assets binding and the catalog baked for this deployment. It does
not add a record engine, provider credentials, or a billing authority.
"""

from __future__ import annotations

import json
import subprocess
from pathlib import Path

from src.domain.hosting.routing_contract import compile_application_runtime_manifest
from src.domain.hosting.runtime.base_runtime_contract import (
    EDGE_FUNCTION_ROUTES,
    assert_runtime_contract,
)

_RUNTIME_PATH = Path(__file__).with_name("project_runtime.mjs")
_SESSION_BRIDGE_PATH = Path(__file__).with_name("session_bridge.mjs")
_BUNDLER_PATH = Path(__file__).with_name("bundle_application.mjs")
_NULL_DISPATCH = "async function dispatchApplication() {\n  return null;\n}\n"

_APPLICATION_FILE_ROUTES = [
    {"id": "application.files.upload", "method": "POST", "public_path": "/api/files", "auth": "session", "upstream_path": "/v1/projects/:project_id/application-files"},
    {"id": "application.files.metadata", "method": "GET", "public_path": "/api/files/:file_id", "auth": "session", "upstream_path": "/v1/projects/:project_id/application-files/:file_id"},
    {"id": "application.files.delete", "method": "DELETE", "public_path": "/api/files/:file_id", "auth": "session", "upstream_path": "/v1/projects/:project_id/application-files/:file_id"},
    {"id": "application.files.download", "method": "GET", "public_path": "/api/files/:file_id/download", "auth": "session", "upstream_path": "/v1/projects/:project_id/application-files/:file_id/download"},
]

_RESERVED_RUNTIME_ROUTES = [
    {"id": "diagnostics.health", "method": "GET", "public_path": "/api/__p2hs/health"},
    *[
        {"id": route["id"], "method": route["method"], "public_path": route["public_path"]}
        for route in EDGE_FUNCTION_ROUTES
    ],
]

_ENTRY = """
const ROUTES = __P2HS_ROUTES__;

function runtimeOwnsRequest(url, request) {
  const path = url.pathname;
  // Session cookies are set on this origin by the runtime. Health, magic-link
  // consume, and the Edge Functions facade stay here so application code cannot
  // answer them or execute a function body inside this Worker.
  if (path === "/api/__p2hs/health") return true;
  if (path === "/api/auth/magic-link/consume" && request.method === "GET") return true;
  if (path === "/api/auth/oauth/callback" && request.method === "GET") return true;
  if (path === "/api/fn" || path.startsWith("/api/fn/")) return true;
  return false;
}

export default {
  async fetch(request, env, ctx) {
    let inbound = request;
    const inboundUrl = new URL(request.url);
    const platformPrefix = inboundUrl.pathname === "/api/_p2hs" || inboundUrl.pathname.startsWith("/api/_p2hs/");
    if (platformPrefix) {
      inboundUrl.pathname = inboundUrl.pathname === "/api/_p2hs"
        ? "/api"
        : "/api" + inboundUrl.pathname.slice("/api/_p2hs".length);
      inbound = new Request(inboundUrl.toString(), request);
    }
    const url = new URL(inbound.url);
    if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
      const platform = {
        contract: P2HS_BASE_RUNTIME_CONTRACT,
        version: P2HS_BASE_RUNTIME_VERSION,
        call(next) {
          return platformCall(inbound, next, env, { routes: ROUTES, fetchImpl: fetch, executionCtx: ctx });
        },
      };
      if (!platformPrefix && !runtimeOwnsRequest(url, inbound)) {
        const handled = await dispatchApplication(inbound, env, ctx, platform);
        if (handled) return handled;
      }
      return platform.call(inbound);
    }
    return env.ASSETS.fetch(request);
  },
};
"""


class ApplicationRouteError(RuntimeError):
    """Pages Functions could not be compiled into the project Worker."""


def session_bridge_source() -> str:
    """Shared session helpers. Prepended to the project runtime and the dispatcher."""
    source = _SESSION_BRIDGE_PATH.read_text(encoding="utf-8").strip() + "\n"
    if "env.CLOUD_SERVICE_TOKEN" in source:
        raise RuntimeError("session bridge must not read the project service token")
    return source


def runtime_source() -> str:
    runtime = session_bridge_source() + _RUNTIME_PATH.read_text(encoding="utf-8")
    if "env.CLOUD_SERVICE_TOKEN" in runtime:
        raise RuntimeError("application runtime must not read the project service token")
    return runtime


def worker_entry(routes: list | None = None) -> str:
    table = list(routes if routes is not None else compile_application_runtime_manifest())
    table.extend(_APPLICATION_FILE_ROUTES)
    entry = _ENTRY.replace("__P2HS_ROUTES__", json.dumps(table, separators=(",", ":")))
    assert_runtime_contract(runtime_source(), entry)
    return entry


def bundler_embed_source() -> str:
    """Bundler body for the GitHub Actions upload script, without its own imports."""
    lines = []
    for line in _BUNDLER_PATH.read_text(encoding="utf-8").splitlines():
        if line.startswith("import "):
            continue
        if line.startswith("export async function "):
            line = line.removeprefix("export ")
        lines.append(line)
    source = "\n".join(lines).strip() + "\n"
    if "${{" in source:
        raise RuntimeError("application bundler must not contain a GitHub expression")
    return source


def application_prelude(repo_root: Path, routes: list | None = None) -> str:
    """Compile ``functions/api`` under ``repo_root``. Missing directory means no app routes."""
    script = (
        "import { pathToFileURL } from 'node:url';\n"
        "const mod = await import(pathToFileURL(process.argv[1]).href);\n"
        "const routes = JSON.parse(process.argv[3] || '[]');\n"
        "process.stdout.write(await mod.bundleApplicationRoutes(process.argv[2], routes));\n"
    )
    platform_routes = list(routes if routes is not None else compile_application_runtime_manifest())
    platform_routes.extend(_APPLICATION_FILE_ROUTES)
    platform_routes.extend(_RESERVED_RUNTIME_ROUTES)
    result = subprocess.run(
        [
            "node", "--input-type=module", "-e", script, str(_BUNDLER_PATH),
            str(repo_root), json.dumps(platform_routes, separators=(",", ":")),
        ],
        capture_output=True,
        text=True,
        encoding="utf-8",
    )
    if result.returncode != 0:
        detail = (result.stderr or result.stdout or "application route bundle failed").strip()
        raise ApplicationRouteError(detail)
    prelude = result.stdout
    if "async function dispatchApplication" not in prelude:
        raise ApplicationRouteError("application route bundle did not define a dispatcher")
    return prelude


def build_worker_source(routes: list | None = None, repo_root: Path | None = None) -> str:
    """Return the ES module uploaded as worker.js. Both deployers must use this."""
    prelude = application_prelude(repo_root, routes=routes) if repo_root is not None else _NULL_DISPATCH
    return "\n".join((
        runtime_source().rstrip(),
        prelude.rstrip(),
        worker_entry(routes).lstrip(),
    ))
