from __future__ import annotations

import asyncio
import os
from pathlib import Path
from typing import Callable, Awaitable, Sequence

from quart import Quart, request, g, send_from_directory

from shared.config import init_config, config, pretty
from shared.models import KV  # ensure shared models imported
# Register all app model classes with SQLAlchemy so cross-domain
# relationship() string references resolve correctly.
for _mod in ("blog.models", "market.models", "cart.models", "events.models", "federation.models"):
    try:
        __import__(_mod)
    except ImportError:
        pass
from shared.log_config import configure_logging
from shared.events import EventProcessor

from shared.db.session import register_db
from shared.browser.app.middleware import register as register_middleware
from shared.browser.app.redis_cacher import register as register_redis
from shared.browser.app.csrf import protect
from shared.browser.app.errors import errors

from .jinja_setup import setup_jinja
from .user_loader import load_current_user


# Async init of config (runs once at import)
asyncio.run(init_config())

BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_DIR = str(BASE_DIR / "static")
TEMPLATE_DIR = str(BASE_DIR / "browser" / "templates")


def create_base_app(
    name: str,
    *,
    context_fn: Callable[[], Awaitable[dict]] | None = None,
    before_request_fns: Sequence[Callable[[], Awaitable[None]]] | None = None,
    domain_services_fn: Callable[[], None] | None = None,
) -> Quart:
    """
    Create a Quart app with shared infrastructure.

    Parameters
    ----------
    name:
        Application name (also used as CACHE_APP_PREFIX).
    context_fn:
        Async function returning a dict for template context.
        Each app provides its own — the cart app queries locally,
        while coop/market apps fetch via internal API.
        If not provided, a minimal default context is used.
    before_request_fns:
        Extra before-request hooks (e.g. cart_loader for the cart app).
    domain_services_fn:
        Callable that registers domain services on the shared registry.
        Each app provides its own — registering real impls for owned
        domains and stubs (or real impls) for others.
    """
    if domain_services_fn is not None:
        domain_services_fn()

    from shared.services.widgets import register_all_widgets
    register_all_widgets()

    app = Quart(
        name,
        static_folder=STATIC_DIR,
        static_url_path="/static",
        template_folder=TEMPLATE_DIR,
    )

    configure_logging(name)

    app.secret_key = os.getenv("SECRET_KEY", "dev-secret-key-change-me-777")

    # Session cookie shared across subdomains
    cookie_domain = os.getenv("SESSION_COOKIE_DOMAIN")  # e.g. ".rose-ash.com"
    if cookie_domain:
        app.config["SESSION_COOKIE_DOMAIN"] = cookie_domain
    app.config["SESSION_COOKIE_NAME"] = "coop_session"

    # Ghost / Redis config
    app.config["GHOST_API_URL"] = os.getenv("GHOST_API_URL")
    app.config["GHOST_PUBLIC_URL"] = os.getenv("GHOST_PUBLIC_URL")
    app.config["GHOST_CONTENT_KEY"] = os.getenv("GHOST_CONTENT_API_KEY")
    app.config["REDIS_URL"] = os.getenv("REDIS_URL")

    # Cache app prefix for key namespacing
    app.config["CACHE_APP_PREFIX"] = name

    # --- infrastructure ---
    register_middleware(app)
    register_db(app)
    register_redis(app)
    setup_jinja(app)
    errors(app)

    # --- before-request hooks ---
    @app.before_request
    async def _route_log():
        g.root = request.headers.get("x-forwarded-prefix", "/")
        g.scheme = request.scheme
        g.host = request.host

    @app.before_request
    async def _load_user():
        await load_current_user()

    # Register any app-specific before-request hooks (e.g. cart loader)
    if before_request_fns:
        for fn in before_request_fns:
            app.before_request(fn)

    @app.before_request
    async def _csrf_protect():
        await protect()

    # --- after-request hooks ---
    @app.after_request
    async def _add_hx_preserve_search_header(response):
        value = request.headers.get("X-Search")
        if value is not None:
            response.headers["HX-Preserve-Search"] = value
        return response

    # --- context processor ---
    if context_fn is not None:
        @app.context_processor
        async def _inject_base():
            return await context_fn()
    else:
        # Minimal fallback (no cart, no menu_items)
        from .context import base_context

        @app.context_processor
        async def _inject_base():
            return await base_context()

    # --- event processor ---
    _event_processor = EventProcessor()

    # --- startup ---
    @app.before_serving
    async def _startup():
        from shared.events.handlers import register_shared_handlers
        register_shared_handlers()
        await init_config()
        print(pretty())
        await _event_processor.start()

    @app.after_serving
    async def _stop_event_processor():
        await _event_processor.stop()

    # --- favicon ---
    @app.get("/favicon.ico")
    async def favicon():
        return await send_from_directory("static", "favicon.ico")

    return app
