"""Platform-owned UI recipes and browser helpers for generated apps.

BFF adapters already proxy Cloud. This layer tells generated UIs how to call
those routes (forms, records, email jobs, magic links, staff dashboards)
without inventing a second backend.
"""
from __future__ import annotations

import hashlib
import json
from pathlib import Path

from src.domain.hosting.runtime.base_runtime_contract import (
    BASE_RUNTIME_CONTRACT,
    BASE_RUNTIME_VERSION,
)

_PAGES_TYPES = frozenset({"html", "react_app", "nuxt_app"})
_RUNTIME = Path(__file__).with_name("runtime")
RECIPE_PATH = "p2hs/app-recipe.json"


def _admin_panel_path(runtime_contract: dict | None) -> str:
    if not runtime_contract:
        return "/admin"
    return (runtime_contract.get("routes") or {}).get("admin_panel_path") or "/admin"


def _render_app_recipe(raw: str, runtime_contract: dict | None) -> str:
    """Patch the resolved admin path + email binding into the shipped recipe.

    Unresolved/legacy call (``runtime_contract=None``) -- emit the template unchanged.
    """
    if not runtime_contract:
        return raw
    recipe = json.loads(raw)
    admin_path = _admin_panel_path(runtime_contract)
    recipes = recipe.get("recipes") or {}
    if "staff_dashboard" in recipes:
        recipes["staff_dashboard"]["path"] = admin_path
    magic = recipes.get("magic_link") or {}
    if magic.get("notes"):
        magic["notes"] = [
            note.replace("(/admin, /be-admin, …)", f"({admin_path})") for note in magic["notes"]
        ]
    email = (runtime_contract.get("capabilities") or {}).get("email") or {}
    send_email = recipes.get("send_email") or {}
    if send_email:
        if email.get("available"):
            send_email["notes"] = [
                *send_email.get("notes", []),
                f"Resolved email capability: provider={email.get('provider')}, "
                f"binding={email.get('binding')}.",
            ]
        else:
            send_email["notes"] = [
                *send_email.get("notes", []),
                "No email capability is currently available for this project — "
                "report the missing capability, do not report the email as sent.",
            ]
    return json.dumps(recipe, indent=2) + "\n"


def app_sdk_files(project_type: str, runtime_contract: dict | None = None) -> dict[str, str]:
    if project_type not in _PAGES_TYPES:
        return {}
    admin_path = _admin_panel_path(runtime_contract)
    client = (_RUNTIME / "p2hs-app-client.js").read_text(encoding="utf-8").replace(
        "__P2HS_ADMIN_PANEL_PATH__", admin_path
    )
    files = {
        RECIPE_PATH: _render_app_recipe(
            (_RUNTIME / "app-recipe.json").read_text(encoding="utf-8"), runtime_contract
        ),
    }
    if project_type == "html":
        runtime_path = "js/p2hs-app.js"
        files[runtime_path] = client
    elif project_type == "react_app":
        runtime_path = "src/lib/p2hs-app.js"
        files[runtime_path] = client
    else:
        runtime_path = "lib/p2hs-app.js"
        files[runtime_path] = client
        files["composables/useP2hs.ts"] = (_RUNTIME / "useP2hs.ts").read_text(
            encoding="utf-8"
        )
    admin_presentation = None
    if runtime_contract:
        from src.domain.codegen.admin_presentation import build_admin_presentation_contract

        admin_presentation = json.dumps(
            build_admin_presentation_contract(runtime_contract), indent=2
        ) + "\n"
        files["p2hs/admin-presentation.json"] = admin_presentation
    components = {
        "auth_session": {
            "ownership": "p2hs_managed",
            "paths": [runtime_path],
            "expected_fingerprint": "sha256:" + hashlib.sha256(client.encode("utf-8")).hexdigest(),
            "extension_policy": "locked",
        }
    }
    if admin_presentation:
        components["admin_presentation"] = {
            "ownership": "p2hs_managed",
            "paths": ["p2hs/admin-presentation.json"],
            "expected_fingerprint": "sha256:" + hashlib.sha256(
                admin_presentation.encode("utf-8")
            ).hexdigest(),
            "extension_policy": "contract_gated",
        }
    files["p2hs/managed-runtime.json"] = json.dumps(
        {
            "schema_version": 1,
            "runtime_contract_version": "auth-session-v4",
            "base_runtime": {
                "contract": BASE_RUNTIME_CONTRACT,
                "version": BASE_RUNTIME_VERSION,
            },
            "generator_version": "p2hs-core-codegen-v1",
            "migration_lineage": ["auth-session-v4", f"{BASE_RUNTIME_CONTRACT}-v{BASE_RUNTIME_VERSION}"],
            "components": components,
        },
        indent=2,
    ) + "\n"
    return files


def _staff_shell_exists(root: Path) -> bool:
    checks = [
        root / "admin.html",
        root / "be-admin.html",
        root / "pages" / "admin",
        root / "pages" / "be-admin",
        root / "app" / "pages" / "admin",
        root / "app" / "pages" / "be-admin",
        root / "src" / "pages" / "Admin.jsx",
        root / "src" / "pages" / "Admin.tsx",
        root / "src" / "pages" / "admin",
        root / "src" / "components" / "P2hsStaffDashboard.jsx",
    ]
    return any(path.exists() for path in checks)


def experience_asset_url(project_type: str) -> str:
    """Same-origin URL of the experience file copied for that starter."""
    if project_type == "html":
        return "./p2hs-application-experience.json"
    return "/p2hs-application-experience.json"


def publish_experience_asset(repo_path: str, project_type: str) -> str:
    """Copy the Core projection to the path the staff shell fetches.

    A missing or incompatible plan publishes ``experience_status: unavailable``
    and an empty module list.
    """
    from src.domain.codegen.application_experience import published_experience

    root = Path(repo_path)
    raw = None
    plan_path = root / "p2hs" / "generation-plan.json"
    if plan_path.is_file():
        try:
            plan = json.loads(plan_path.read_text(encoding="utf-8"))
            raw = plan.get("experience") if isinstance(plan, dict) else None
        except (OSError, json.JSONDecodeError, UnicodeDecodeError):
            raw = None
    document = published_experience(raw)
    text = json.dumps(document, indent=2) + "\n"
    (root / "p2hs").mkdir(parents=True, exist_ok=True)
    (root / "p2hs" / "application-experience.json").write_text(text, encoding="utf-8")
    if project_type == "html":
        target = root / "p2hs-application-experience.json"
    else:
        target = root / "public" / "p2hs-application-experience.json"
        target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(text, encoding="utf-8")
    return experience_asset_url(project_type)


def _admin_application_name(runtime_contract: dict | None) -> str:
    presentation = (runtime_contract or {}).get("presentation") or {}
    branding = presentation.get("branding") or {}
    return str(branding.get("application_name") or "Application").strip() or "Application"


def _admin_auth_mode(runtime_contract: dict | None) -> str:
    """Which sign-in form the seeded shell renders: 'password', 'magic_link', or 'both'.

    Falls back to 'magic_link' (the historical hardcoded behavior) when the
    contract is unresolved or declares neither mode explicitly, since every
    variant of the seeded shell structurally requires at least one (see
    ``_required_auth_modes`` in runtime_contract.py).
    """
    auth = ((runtime_contract or {}).get("capabilities") or {}).get("auth") or {}
    password = bool(auth.get("password"))
    magic_link = bool(auth.get("magic_link"))
    if password and magic_link:
        return "both"
    if password:
        return "password"
    return "magic_link"


def staff_shell_files(
    project_type: str, repo_path: str, runtime_contract: dict | None = None
) -> dict[str, str]:
    """Seed a staff inbox page only when the app has no admin surface yet.

    Named at the project's resolved admin path when one is set; falls back to the
    historical /admin location (and filenames) when runtime_contract is None/default,
    so an unresolved call is unaffected.
    """
    if project_type not in _PAGES_TYPES:
        return {}
    root = Path(repo_path)
    if _staff_shell_exists(root):
        return {}
    admin_path = _admin_panel_path(runtime_contract).strip("/") or "admin"
    app_name = _admin_application_name(runtime_contract)
    auth_mode = _admin_auth_mode(runtime_contract)

    def _fill(content: str) -> str:
        return (
            content.replace("__P2HS_APP_NAME__", app_name)
            .replace("__P2HS_AUTH_MODE__", auth_mode)
            .replace("__P2HS_EXPERIENCE_URL__", experience_asset_url(project_type))
        )

    if project_type == "html":
        name = "admin.html" if admin_path == "admin" else f"{admin_path}.html"
        content = (_RUNTIME / "staff-admin.html").read_text(encoding="utf-8")
        return {name: _fill(content)}
    if project_type == "react_app":
        # File-system-routed frameworks need a path-shaped filename; React's own
        # router is app-code, not file-based, so the component name stays fixed.
        content = (_RUNTIME / "P2hsStaffDashboard.jsx").read_text(encoding="utf-8")
        return {"src/components/P2hsStaffDashboard.jsx": _fill(content)}
    content = (_RUNTIME / "staff-admin.vue").read_text(encoding="utf-8")
    return {f"pages/{admin_path}/index.vue": _fill(content)}


def ensure_pack_manifest(repo_path: str, pack_slug: str, notify_to_email: str = "") -> None:
    """Ensure a pack slug is declared so worker install does not depend on the agent."""
    path = Path(repo_path) / "p2hs" / "feature-packs.json"
    data: dict = {"version": 1, "install": []}
    if path.is_file():
        try:
            loaded = json.loads(path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError, UnicodeDecodeError):
            loaded = None
        if isinstance(loaded, dict) and isinstance(loaded.get("install"), list):
            data = loaded
            data.setdefault("version", 1)
    installs = data.setdefault("install", [])
    notify = str(notify_to_email or "").strip()
    for row in installs:
        if not isinstance(row, dict) or row.get("pack_slug") != pack_slug:
            continue
        cfg = row.get("config")
        if not isinstance(cfg, dict):
            cfg = {}
            row["config"] = cfg
        if notify and not str(cfg.get("notify_to_email") or "").strip():
            cfg["notify_to_email"] = notify
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8", newline="\n")
        return
    config: dict = {}
    if notify:
        config["notify_to_email"] = notify
    installs.append({"pack_slug": pack_slug, "config": config})
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8", newline="\n")


def ensure_contact_capture_manifest(repo_path: str, notify_to_email: str = "") -> None:
    """Leftover-contact migration helper. New runs use the App Block plan instead."""
    ensure_pack_manifest(repo_path, "contact-capture", notify_to_email=notify_to_email)


def validate_app_block_plan_for_execution(repo_path: str):
    """Read and gate a present artifact before any install-side file changes."""
    from src.domain.app_blocks.execution import assert_install_plan_installable
    from src.domain.app_blocks.schemas import AppBlockPlan

    path = Path(repo_path) / "p2hs" / "app-blocks-plan.json"
    if not path.is_file():
        return None
    plan = AppBlockPlan.model_validate_json(path.read_text(encoding="utf-8"))
    assert_install_plan_installable(plan)
    return plan


def ensure_required_packs_from_plan(repo_path: str) -> None:
    plan = validate_app_block_plan_for_execution(repo_path)
    if plan is None or plan.adoption != "authoritative":
        return
    for slug in plan.required_feature_packs:
        if isinstance(slug, str) and slug:
            ensure_pack_manifest(repo_path, slug)
