from __future__ import annotations

import hashlib
import shutil
from pathlib import Path

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles

from .auth import EditorAuthManager, create_editor_auth_router
from .manager import UIBuilderManager
from .router import DataProvider, create_ui_builder_router


def _seed_config(source: Path, destination: Path) -> None:
    if destination.exists():
        return
    if source.exists():
        destination.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source, destination)


def install_ui_builder(
    app: FastAPI,
    *,
    build_version: str = "",
    settings_dir: Path,
    data_providers: dict[str, DataProvider],
    source_labels: dict[str, str] | None = None,
    api_prefix: str = "/api/ui-builder",
    assets_url: str = "/ui-builder-assets",
    editor_url: str = "/editor",
    runtime_url: str = "/",
    settings_filename: str = "ui_builder.json",
    draft_filename: str = "ui_builder_draft.json",
    published_filename: str = "ui_builder_published.json",
    custom_script_urls: list[str] | None = None,
    custom_style_urls: list[str] | None = None,
) -> UIBuilderManager:
    """Install published runtime and PIN-protected editor."""

    settings_dir = Path(settings_dir)
    legacy = settings_dir / Path(settings_filename).name
    draft_file = settings_dir / Path(draft_filename).name
    published_file = settings_dir / Path(published_filename).name

    _seed_config(legacy, draft_file)
    _seed_config(legacy, published_file)

    draft_manager = UIBuilderManager(settings_dir, filename=draft_file.name)
    published_manager = UIBuilderManager(settings_dir, filename=published_file.name)
    auth = EditorAuthManager(settings_dir)

    assets_dir = Path(__file__).resolve().parent / "static"
    app.mount(
        assets_url,
        StaticFiles(directory=assets_dir),
        name="ui-builder-assets",
    )

    editor_api = f"{api_prefix.rstrip('/')}/editor"
    runtime_api = f"{api_prefix.rstrip('/')}/runtime"
    auth_api = f"{api_prefix.rstrip('/')}/auth"

    app.include_router(
        create_editor_auth_router(
            auth,
            prefix=auth_api,
            editor_url=editor_url,
        )
    )
    app.include_router(
        create_ui_builder_router(
            draft_manager,
            data_providers,
            source_labels=source_labels,
            prefix=editor_api,
            access_dependency=auth.require_editor,
            publish_manager=published_manager,
        )
    )
    app.include_router(
        create_ui_builder_router(
            published_manager,
            data_providers,
            source_labels=source_labels,
            prefix=runtime_api,
            read_only=True,
        )
    )

    editor_template = (assets_dir / "index.html").read_text(encoding="utf-8")
    runtime_template = (assets_dir / "runtime.html").read_text(encoding="utf-8")
    asset_digest = hashlib.sha256()
    for asset_name in ("app.js", "styles.css"):
        asset_path = assets_dir / asset_name
        if asset_path.exists():
            asset_digest.update(asset_path.read_bytes())
    asset_version = asset_digest.hexdigest()[:12]
    custom_script_urls = custom_script_urls or []
    custom_style_urls = custom_style_urls or []

    def render_page(mode: str) -> HTMLResponse:
        page_api = editor_api if mode == "editor" else runtime_api
        # Runtime has its own HTML entry point and contains no Builder DOM at all.
        # This prevents the visual editor from flashing during normal page load.
        template = editor_template if mode == "editor" else runtime_template
        html = (
            template.replace("__UI_BUILDER_ASSETS__", assets_url.rstrip("/"))
            .replace("__UI_BUILDER_ASSET_VERSION__", asset_version)
            .replace("__UI_BUILDER_BUILD_VERSION__", str(build_version or asset_version))
            .replace("__UI_BUILDER_API__", page_api)
            .replace("__UI_BUILDER_AUTH_API__", auth_api)
            .replace("__UI_BUILDER_EDITOR__", editor_url)
            .replace("__UI_BUILDER_RUNTIME__", runtime_url)
            .replace("__UI_BUILDER_MODE__", mode)
            .replace("__UI_BUILDER_EDITOR_HOTKEY__", "Ctrl+Shift+E")
            .replace(
                "__UI_BUILDER_CUSTOM_STYLES__",
                "\n".join(
                    f'<link rel="stylesheet" href="{url}">'
                    for url in custom_style_urls
                ),
            )
            .replace(
                "__UI_BUILDER_CUSTOM_SCRIPTS__",
                "\n".join(
                    f'<script src="{url}"></script>'
                    for url in custom_script_urls
                ),
            )
        )
        return HTMLResponse(html)

    @app.get(editor_url, include_in_schema=False)
    async def ui_builder_editor(request: Request):
        # Runtime is the default landing mode, even if an old bookmark points
        # directly at /editor. The visual editor is opened only explicitly
        # from the runtime toolbar and carries ?open=1 after PIN auth.
        if request.query_params.get("open") != "1":
            return RedirectResponse(runtime_url, status_code=302)
        if not auth.is_request_authenticated(request):
            return RedirectResponse(runtime_url, status_code=302)
        return render_page("editor")

    @app.get(runtime_url, include_in_schema=False)
    async def ui_builder_runtime() -> HTMLResponse:
        return render_page("runtime")

    app.state.ui_builder_manager = draft_manager
    app.state.ui_builder_draft_manager = draft_manager
    app.state.ui_builder_published_manager = published_manager
    app.state.ui_builder_auth = auth
    return draft_manager
