"""FastAPI entrypoint. Runs Alembic migrations on startup, bootstraps the
feeds table from TOML, mounts the API + HTML routers.
"""
from __future__ import annotations

import asyncio
from contextlib import asynccontextmanager
from pathlib import Path

from alembic import command
from alembic.config import Config as AlembicConfig
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles

from app import branding
from app.config import get_settings
from app.db import get_session_factory
from app.logging import configure_logging, get_logger
from app.routers import api as api_router
from app.routers import auth as auth_router
from app.routers import chat as chat_router
from app.routers import email as email_router
from app.routers import ops as ops_router
from app.routers import pages as pages_router
from app.routers import polar_webhook as polar_webhook_router
from app.routers import public as public_router
from app.routers import stripe_billing as stripe_router
from app.routers import sync as sync_router
from app.routers import ticker_validate as ticker_validate_router
from app.routers import universe as universe_router
from app.services.feeds_bootstrap import bootstrap_feeds


log = get_logger("cassandra")
APP_DIR = Path(__file__).resolve().parent
PROJECT_DIR = APP_DIR.parent


def _run_migrations() -> None:
    """Synchronous Alembic upgrade. Called once at lifespan startup."""
    cfg = AlembicConfig(str(PROJECT_DIR / "alembic.ini"))
    cfg.set_main_option("script_location", str(PROJECT_DIR / "alembic"))
    cfg.set_main_option("sqlalchemy.url", get_settings().DATABASE_URL)
    command.upgrade(cfg, "head")


@asynccontextmanager
async def lifespan(app: FastAPI):
    configure_logging()
    log.info("cassandra.startup")
    s = get_settings()
    if not s.PORTFOLIO_SYNC_PEPPER and not s.DATABASE_URL.startswith("sqlite"):
        # Outer wrap still works (it just degrades to a per-user derived
        # key with no shared secret), but a DB leak would let an attacker
        # brute-force the PIN offline. Loud warning, not a hard failure.
        log.warning("cassandra.portfolio_sync.pepper_missing")
    try:
        # Alembic's env.py uses asyncio.run() internally; offload it to a
        # worker thread so it doesn't collide with FastAPI's running loop.
        await asyncio.to_thread(_run_migrations)
        log.info("cassandra.migrations.applied")
    except Exception as e:
        log.error("cassandra.migrations.failed", error=str(e))
        raise
    async with get_session_factory()() as session:
        inserted = await bootstrap_feeds(session)
        log.info("cassandra.feeds.bootstrap", inserted=inserted)
    # Load public-page translation YAMLs into memory once at startup.
    # Lazy-loaded otherwise, but pre-loading lets startup fail loudly
    # if a YAML file is corrupted instead of failing on the first
    # landing-page request.
    from app.services.locales import load_locales
    load_locales()
    yield
    log.info("cassandra.shutdown")


app = FastAPI(
    title=branding.BRAND_NAME,
    description="Macro-strategy dashboard",
    version="0.1.0",
    lifespan=lifespan,
)

# Gzip responses ≥500 bytes when the client sends Accept-Encoding: gzip.
# The Phase G universe payload is repetitive JSON that gzips to ~25-30%
# of raw size; compression is mandatory for that endpoint to be cheap.
app.add_middleware(GZipMiddleware, minimum_size=500)

app.mount(
    "/static",
    StaticFiles(directory=str(APP_DIR / "static")),
    name="static",
)

app.include_router(auth_router.router, tags=["auth"])
app.include_router(email_router.router, tags=["email"])
app.include_router(api_router.router, prefix="/api", tags=["api"])
app.include_router(chat_router.router, prefix="/api", tags=["chat"])
app.include_router(ops_router.router, prefix="/api", tags=["ops"])
app.include_router(universe_router.router, prefix="/api", tags=["universe"])
app.include_router(ticker_validate_router.router, prefix="/api", tags=["ticker-validate"])
app.include_router(sync_router.router, tags=["portfolio-sync"])
# Polar webhook (no bearer-token auth — authenticity via HMAC). Path
# `/api/polar/webhook` is set on the route itself so the URL Polar
# stores remains stable even if api_router's prefix ever moves.
app.include_router(polar_webhook_router.router, tags=["polar-webhook"])
# Stripe billing (checkout, portal, webhook). Auth lives per-route:
# checkout + portal require_auth, webhook is signature-gated.
app.include_router(stripe_router.router, tags=["stripe-billing"])
# Public router (no auth dep) before pages_router so the marketing/legal
# paths can never collide with future authenticated routes.
app.include_router(public_router.router)
app.include_router(pages_router.router, tags=["pages"])
