"""Cloud auth-users BFF for generated apps. Replaces local /api/admin/users stores."""

from __future__ import annotations

from pathlib import Path

from src.domain.codegen.local_backup import move_to_bak
from src.domain.codegen.record_adapter import install_record_adapter, record_adapter_files

_PAGES_TYPES = frozenset({"html", "react_app", "nuxt_app"})
BACKUP_SUFFIX = ".p2hs-local.bak"
ADAPTER_MARKER = "p2hs-auth-users"
PASSWORD_ADAPTER_MARKER = "p2hs-auth-password"

_LOCAL_ROUTE_CANDIDATES = (
    "server/api/admin/users.ts",
    "server/api/admin/users.js",
    "server/api/admin/users.get.ts",
    "server/api/admin/users.post.ts",
    "server/api/admin/users/index.ts",
    "server/api/admin/users/index.js",
    "server/api/admin/users/index.get.ts",
    "server/api/admin/users/index.post.ts",
    "functions/api/admin/users.js",
    "functions/api/admin/users.ts",
    "functions/api/admin/users/index.js",
)

_LOCAL_PASSWORD_ROUTES = (
    "server/api/auth/forgot-password.ts",
    "server/api/auth/forgot-password.js",
    "server/api/auth/forgot-password.post.ts",
    "server/api/auth/forgot-password.post.js",
    "server/api/auth/reset-password.ts",
    "server/api/auth/reset-password.js",
    "server/api/auth/reset-password.post.ts",
    "server/api/auth/reset-password.post.js",
    "functions/api/auth/forgot-password.js",
    "functions/api/auth/forgot-password.ts",
    "functions/api/auth/reset-password.js",
    "functions/api/auth/reset-password.ts",
)


def password_admitted(runtime_contract: dict | None) -> bool | None:
    """Return the explicit password decision from a resolved runtime contract.

    ``None`` deliberately means legacy/unresolved input.  Callers may retain their
    historical entitlement-based projection until Core has supplied the contract;
    an explicit passwordless contract must always win over entitlement defaults.
    """
    if not isinstance(runtime_contract, dict):
        return None
    auth = (runtime_contract.get("capabilities") or {}).get("auth") or {}
    if isinstance(auth.get("password"), bool):
        return auth["password"]
    methods = {str(value).strip().lower() for value in (auth.get("methods") or [])}
    return "password" in methods


def auth_users_adapter_files(
    project_type: str,
    runtime_contract: dict | None = None,
) -> dict[str, str]:
    if project_type not in _PAGES_TYPES:
        return {}
    root = Path(__file__).with_name("runtime")
    admitted = password_admitted(runtime_contract)
    # The unresolved projection remains backward compatible.  Resolved projects
    # carry the decision into every managed auth surface; this avoids deriving
    # password capability from the broad port entitlement set.
    password_flag = "true" if admitted is not False else "false"
    shared = (root / "p2hs-auth-users.mjs").read_text(encoding="utf-8").replace(
        "__P2HS_PASSWORD_ADMITTED__", password_flag
    )
    password = (root / "p2hs-auth-password.mjs").read_text(encoding="utf-8").replace(
        "__P2HS_PASSWORD_ADMITTED__", password_flag
    )
    files = {
        "functions/_lib/p2hs-auth-users.mjs": shared,
        "functions/_lib/p2hs-auth-password.mjs": password,
        "functions/api/admin/users.js": (root / "auth-users-pages-fn.js").read_text(
            encoding="utf-8"
        ),
    }
    if admitted is not False:
        files.update({
            "functions/api/auth/forgot-password.js": (
                root / "auth-password-forgot-pages-fn.js"
            ).read_text(encoding="utf-8"),
            "functions/api/auth/reset-password.js": (
                root / "auth-password-reset-pages-fn.js"
            ).read_text(encoding="utf-8"),
        })
    if project_type == "nuxt_app":
        files["server/utils/p2hs-auth-users.mjs"] = shared
        files["server/utils/p2hs-auth-password.mjs"] = password
        files["server/api/admin/users.ts"] = (root / "auth-users-route.ts").read_text(
            encoding="utf-8"
        )
        files["server/api/admin/users/[...path].ts"] = files["server/api/admin/users.ts"].replace(
            'from "../../utils/', 'from "../../../utils/'
        )
        if admitted is not False:
            files["server/api/auth/forgot-password.ts"] = (
                root / "auth-password-forgot-route.ts"
            ).read_text(encoding="utf-8")
            files["server/api/auth/forgot-password.post.ts"] = files[
                "server/api/auth/forgot-password.ts"
            ]
            files["server/api/auth/reset-password.ts"] = (
                root / "auth-password-reset-route.ts"
            ).read_text(encoding="utf-8")
            files["server/api/auth/reset-password.post.ts"] = files[
                "server/api/auth/reset-password.ts"
            ]
    files["functions/api/admin/users/[[path]].js"] = files["functions/api/admin/users.js"].replace(
        'from "../../_lib/', 'from "../../../_lib/'
    )
    return files


def local_admin_user_routes(repo_path: str) -> list[Path]:
    root = Path(repo_path)
    found: list[Path] = []
    for rel in _LOCAL_ROUTE_CANDIDATES:
        path = root / rel
        if path.is_file():
            found.append(path)
    for directory in (root / "server/api/admin/users", root / "functions/api/admin/users"):
        if directory.is_dir():
            found.extend(
                path
                for path in directory.rglob("*")
                if path.suffix in {".ts", ".js"} and path.is_file()
            )
    return sorted(set(found))


def _is_platform_adapter(path: Path) -> bool:
    try:
        text = path.read_text(encoding="utf-8")
    except OSError:
        return False
    return ADAPTER_MARKER in text or PASSWORD_ADAPTER_MARKER in text


def local_password_routes(repo_path: str) -> list[Path]:
    root = Path(repo_path)
    found: list[Path] = []
    for rel in _LOCAL_PASSWORD_ROUTES:
        path = root / rel
        if path.is_file():
            found.append(path)
    return sorted(set(found))


def remove_unadmitted_password_routes(
    repo_path: str,
    runtime_contract: dict | None,
) -> list[str]:
    """Remove only P2HS-owned public password routes for an explicit passwordless target.

    A customer-authored route is a hard boundary violation, never something the
    migration silently deletes. The operation is intentionally narrow so it can
    be used by a disposable migration executor and by a future certified apply.
    """
    if password_admitted(runtime_contract) is not False:
        return []
    removed: list[str] = []
    for path in local_password_routes(repo_path):
        if not _is_platform_adapter(path):
            raise ValueError(
                f"passwordless_migration_customer_route:{path.relative_to(Path(repo_path)).as_posix()}"
            )
        path.unlink()
        removed.append(path.relative_to(Path(repo_path)).as_posix())
    return removed


def backup_local_user_routes(repo_path: str) -> list[str]:
    """Rename homemade admin-users and password routes so Cloud adapters own the paths."""
    backed: list[str] = []
    for path in [*local_admin_user_routes(repo_path), *local_password_routes(repo_path)]:
        if _is_platform_adapter(path):
            continue
        dest = move_to_bak(Path(repo_path), path.relative_to(Path(repo_path)).as_posix())
        backed.append(dest)
    return backed


def install_auth_users_adapter(
    repo_path: str,
    project_type: str,
    *,
    replace_local: bool = False,
) -> dict[str, list[str]]:
    """Install Cloud auth-users BFF. Retire local /api/admin/users when asked."""
    written: list[str] = []
    skipped: list[str] = []
    backed = backup_local_user_routes(repo_path) if replace_local else []
    records = install_record_adapter(
        repo_path,
        project_type,
        replace_local=replace_local,
    )
    for name, content in auth_users_adapter_files(project_type).items():
        path = Path(repo_path) / name
        if path.exists() and not replace_local and not _is_platform_adapter(path):
            skipped.append(name)
            continue
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content, encoding="utf-8", newline="\n")
        written.append(name)
    return {
        "written": written + list(records.get("written") or []),
        "backed_up": backed + list(records.get("backed_up") or []),
        "skipped": skipped + list(records.get("skipped") or []),
    }


def adapter_status(repo_path: str, project_type: str) -> dict[str, bool]:
    root = Path(repo_path)
    records = {rel: (root / rel).is_file() for rel in record_adapter_files(project_type)}
    auth = {
        rel: (root / rel).is_file() and _is_platform_adapter(root / rel)
        for rel in auth_users_adapter_files(project_type)
    }
    return {
        "record_adapter": bool(records) and all(records.values()),
        "auth_users_adapter": bool(auth) and all(auth.values()),
        "local_admin_users_route": bool(local_admin_user_routes(repo_path)),
    }
