"""
Art-DAG L2 Server Application Factory.

Creates and configures the FastAPI application with all routers and middleware.
"""

import secrets
import time
from pathlib import Path
from contextlib import asynccontextmanager
from urllib.parse import quote

import httpx
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse

from artdag_common import create_jinja_env
from artdag_common.middleware.auth import get_user_from_cookie

from .config import settings

# Paths that should never trigger a silent auth check
_SKIP_PREFIXES = ("/auth/", "/.well-known/", "/health",
                  "/internal/", "/static/", "/inbox")
_SILENT_CHECK_COOLDOWN = 300  # 5 minutes
_DEVICE_COOKIE = "artdag_did"
_DEVICE_COOKIE_MAX_AGE = 30 * 24 * 3600  # 30 days

# Derive external base URL from oauth_redirect_uri (e.g. https://artdag.rose-ash.com)
_EXTERNAL_BASE = settings.oauth_redirect_uri.rsplit("/auth/callback", 1)[0]


def _external_url(request: Request) -> str:
    """Build external URL from request path + query, using configured base domain."""
    url = f"{_EXTERNAL_BASE}{request.url.path}"
    if request.url.query:
        url += f"?{request.url.query}"
    return url


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Manage database connection pool lifecycle."""
    import db
    await db.init_pool()
    yield
    await db.close_pool()


def create_app() -> FastAPI:
    """
    Create and configure the L2 FastAPI application.

    Returns:
        Configured FastAPI instance
    """
    app = FastAPI(
        title="Art-DAG L2 Server",
        description="ActivityPub server for Art-DAG ownership and federation",
        version="1.0.0",
        lifespan=lifespan,
    )

    # Silent auth check — auto-login via prompt=none OAuth
    # NOTE: registered BEFORE device_id so device_id is outermost (runs first)
    @app.middleware("http")
    async def silent_auth_check(request: Request, call_next):
        path = request.url.path
        if (
            request.method != "GET"
            or any(path.startswith(p) for p in _SKIP_PREFIXES)
            or request.headers.get("hx-request")  # skip HTMX
        ):
            return await call_next(request)

        # Already logged in — verify account hasn't revoked auth
        if get_user_from_cookie(request):
            device_id = getattr(request.state, "device_id", None)
            if device_id:
                # Check every 30s whether account still considers this device active
                check_at = request.cookies.get("auth_check_at")
                now = time.time()
                stale = True
                if check_at:
                    try:
                        stale = (now - float(check_at)) > 30
                    except (ValueError, TypeError):
                        pass
                if stale and settings.internal_account_url:
                    try:
                        async with httpx.AsyncClient(timeout=3) as client:
                            resp = await client.get(
                                f"{settings.internal_account_url.rstrip('/')}"
                                f"/auth/internal/check-device"
                                f"?device_id={device_id}&app=artdag_l2"
                            )
                            if resp.status_code == 200 and not resp.json().get("active"):
                                # Account revoked — clear cookie
                                response = await call_next(request)
                                response.delete_cookie("artdag_session")
                                response.delete_cookie("pnone_at")
                                response.delete_cookie("auth_check_at")
                                return response
                    except Exception:
                        pass
                    # Update check timestamp
                    response = await call_next(request)
                    response.set_cookie(
                        "auth_check_at", str(now), max_age=60,
                        httponly=True, samesite="lax", secure=True,
                    )
                    return response
            return await call_next(request)

        # Check cooldown — don't re-check within 5 minutes
        pnone_at = request.cookies.get("pnone_at")
        if pnone_at:
            try:
                pnone_ts = float(pnone_at)
                if (time.time() - pnone_ts) < _SILENT_CHECK_COOLDOWN:
                    return await call_next(request)
            except (ValueError, TypeError):
                pass

        # Redirect to silent OAuth check
        current_url = _external_url(request)
        return RedirectResponse(
            url=f"/auth/login?prompt=none&next={quote(current_url, safe='')}",
            status_code=302,
        )

    # Device ID middleware — track browser identity across domains
    # Registered AFTER silent_auth_check so it's outermost (always runs)
    @app.middleware("http")
    async def device_id_middleware(request: Request, call_next):
        did = request.cookies.get(_DEVICE_COOKIE)
        if did:
            request.state.device_id = did
            request.state._new_device_id = False
        else:
            request.state.device_id = secrets.token_urlsafe(32)
            request.state._new_device_id = True

        response = await call_next(request)

        if getattr(request.state, "_new_device_id", False):
            response.set_cookie(
                key=_DEVICE_COOKIE,
                value=request.state.device_id,
                max_age=_DEVICE_COOKIE_MAX_AGE,
                httponly=True,
                samesite="lax",
                secure=True,
            )
        return response

    # Coop fragment pre-fetch — inject nav-tree, auth-menu, cart-mini
    _FRAG_SKIP = ("/auth/", "/.well-known/", "/health",
                   "/internal/", "/static/", "/inbox")

    @app.middleware("http")
    async def coop_fragments_middleware(request: Request, call_next):
        path = request.url.path
        if (
            request.method != "GET"
            or any(path.startswith(p) for p in _FRAG_SKIP)
            or request.headers.get("hx-request")
        ):
            request.state.nav_tree_html = ""
            request.state.auth_menu_html = ""
            request.state.cart_mini_html = ""
            return await call_next(request)

        from artdag_common.fragments import fetch_fragments as _fetch_frags

        user = get_user_from_cookie(request)
        auth_params = {"email": user.email or user.username} if user else {}
        nav_params = {"app_name": "artdag", "path": path}

        try:
            nav_tree_html, auth_menu_html, cart_mini_html = await _fetch_frags([
                ("blog", "nav-tree", nav_params),
                ("account", "auth-menu", auth_params or None),
                ("cart", "cart-mini", None),
            ])
        except Exception:
            nav_tree_html = auth_menu_html = cart_mini_html = ""

        request.state.nav_tree_html = nav_tree_html
        request.state.auth_menu_html = auth_menu_html
        request.state.cart_mini_html = cart_mini_html

        return await call_next(request)

    # Initialize Jinja2 templates
    template_dir = Path(__file__).parent / "templates"
    app.state.templates = create_jinja_env(template_dir)

    # Health check (skips auth middleware via _SKIP_PREFIXES)
    @app.get("/health")
    async def health():
        return JSONResponse({"status": "ok"})

    # Custom 404 handler
    @app.exception_handler(404)
    async def not_found_handler(request: Request, exc):
        from artdag_common.middleware import wants_html
        if wants_html(request):
            from artdag_common import render
            return render(app.state.templates, "404.html", request,
                user=None,
            )
        return JSONResponse({"detail": "Not found"}, status_code=404)

    # Include routers
    from .routers import auth, assets, activities, anchors, storage, users, renderers

    # Root routes
    app.include_router(auth.router, prefix="/auth", tags=["auth"])
    app.include_router(users.router, tags=["users"])

    # Feature routers
    app.include_router(assets.router, prefix="/assets", tags=["assets"])
    app.include_router(activities.router, prefix="/activities", tags=["activities"])
    app.include_router(anchors.router, prefix="/anchors", tags=["anchors"])
    app.include_router(storage.router, prefix="/storage", tags=["storage"])
    app.include_router(renderers.router, prefix="/renderers", tags=["renderers"])

    # WebFinger and ActivityPub discovery
    from .routers import federation
    app.include_router(federation.router, tags=["federation"])

    return app


# Create the default app instance
app = create_app()
