"""Quart app factory."""

import logging
from pathlib import Path

from quart import Quart, request

from .api import all_blueprints
from .config import get_config
from .frontend import frontend_bp
from .services.credential_crypto import CredentialCrypto

# Browser-extension origins. The FabledCurator extension fetches from
# moz-extension://<uuid>/ on Firefox and chrome-extension://<uuid>/ on
# Chromium-based browsers. Operator-flagged 2026-05-26: extension's
# 'Test connection' returned `NetworkError` because the X-Extension-Key
# header on /api/credentials triggers a CORS preflight that our routes
# don't handle. Whitelisting only these two schemes (not opening CORS
# up generally) lets the extension talk to a plain-HTTP self-hosted FC
# without weakening the no-CORS posture for normal browser usage.
_EXTENSION_ORIGIN_SCHEMES = ("moz-extension://", "chrome-extension://")

_CREDENTIAL_KEY_PATH = Path("/images/secrets/credential_key.b64")


def create_app() -> Quart:
    cfg = get_config()
    logging.basicConfig(level=cfg.log_level)

    # Bootstrap the credential encryption key file (mode 0600) before
    # any request can land. FC-3b: file-based system-generated key.
    CredentialCrypto(_CREDENTIAL_KEY_PATH)

    app = Quart(__name__)
    app.secret_key = cfg.secret_key

    # Stream files in 4 MiB chunks instead of Quart's 8 KiB default. The image
    # library lives on a CIFS/SMB share (mounted rsize=4 MiB), so 8 KiB reads
    # meant ~19k network round-trips for one large original — 30–58s downloads
    # that starved both the GPU agent and the browser (operator-flagged
    # 2026-07-01). 4 MiB matches the mount's read size → one round-trip per read,
    # ~500× fewer. buffer_size is the MAX read, so small thumbnails still read in
    # a single gulp, and Range/mime/ETag/conditional handling lives on Response,
    # so this keeps all of it. Guarded so a future Quart-internal change can't
    # break boot — worst case we fall back to the slow default.
    try:
        from quart.wrappers.response import FileBody
        FileBody.buffer_size = 4 * 1024 * 1024
    except Exception:
        logging.getLogger(__name__).warning(
            "could not raise FileBody.buffer_size — file serving stays on 8 KiB chunks"
        )

    for bp in all_blueprints():
        app.register_blueprint(bp)
    # Registered last so /api/* routes win over the SPA catch-all.
    app.register_blueprint(frontend_bp)

    @app.before_request
    async def _extension_cors_preflight():
        # Short-circuit OPTIONS preflight from the browser extension with a
        # 204 + CORS headers (the after_request hook below adds them).
        # Without this, OPTIONS lands on routes that only declared POST/GET
        # methods and 405s before the after_request gets a chance.
        if request.method != "OPTIONS":
            return None
        origin = request.headers.get("Origin", "")
        if any(origin.startswith(s) for s in _EXTENSION_ORIGIN_SCHEMES):
            return "", 204
        return None

    @app.after_request
    async def _extension_cors_headers(response):
        origin = request.headers.get("Origin", "")
        if any(origin.startswith(s) for s in _EXTENSION_ORIGIN_SCHEMES):
            response.headers["Access-Control-Allow-Origin"] = origin
            response.headers["Access-Control-Allow-Methods"] = (
                "GET, POST, PATCH, DELETE, OPTIONS"
            )
            response.headers["Access-Control-Allow-Headers"] = (
                "Content-Type, X-Extension-Key"
            )
            response.headers["Access-Control-Max-Age"] = "86400"
        return response

    @app.after_serving
    async def _dispose_db_engine() -> None:
        from .extensions import dispose_engine
        await dispose_engine()

    return app
