"""
FastAPI Data Gateway — application entry point.

Startup sequence:
  1. Configure structured logging (before any log.info calls)
  2. Validate Settings (Pydantic raises at startup if env is missing/invalid)
  3. Register middlewares (CORS, trusted host, rate-limit error handler)
  4. Mount all routers
  5. Start APScheduler (interval jobs with active-day / time-window guards)
  6. Expose /health liveness probe

Security middleware notes:
  • CORS: allow_credentials=True required for HttpOnly cookie exchange.
    Set allow_origins to your exact frontend domain — never "*" with credentials.
  • Docs (Swagger / ReDoc) are disabled in production to reduce attack surface.
  • /internal/* routes must be blocked at your nginx/ALB layer for public traffic.
    FastAPI cannot enforce network-level restrictions — that is the proxy's job.
"""

from __future__ import annotations

from contextlib import asynccontextmanager
from typing import AsyncGenerator

import structlog
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded

from app.config import get_settings
from app.core.logging import configure_logging
from app.core.rate_limit import data_limiter, limiter
from app.core.redis_client import close_redis
from app.routers import admin, auth, data, internal
from app.scheduler.runner import build_scheduler

settings = get_settings()


# ---------------------------------------------------------------------------
# Application lifespan
# ---------------------------------------------------------------------------

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """Startup and shutdown hooks."""
    configure_logging(settings.log_level, settings.log_format)
    log = structlog.get_logger("app")
    log.info("startup", environment=settings.environment, timezone=settings.timezone)

    # Start scheduler — jobs fire at their configured intervals;
    # the active-day / time-window guard inside each job handles skipping.
    scheduler = build_scheduler()
    scheduler.start()
    log.info("scheduler_started", job_count=len(scheduler.get_jobs()))

    yield  # application runs here

    scheduler.shutdown(wait=False)
    log.info("scheduler_stopped")

    await close_redis()
    log.info("shutdown")


# ---------------------------------------------------------------------------
# FastAPI application
# ---------------------------------------------------------------------------

app = FastAPI(
    title="FastAPI Data Gateway",
    version="1.0.0",
    description=(
        "Authenticated data delivery gateway supporting:\n"
        "- Browser/mobile users (JWT + refresh-token rotation)\n"
        "- External subscribers (HMAC-signed API keys, WebSocket push)\n"
        "- Internal cron jobs (static API key, VPC-only)\n"
    ),
    # Disable interactive docs in production — reduce attack surface
    docs_url="/docs" if not settings.is_production else None,
    redoc_url="/redoc" if not settings.is_production else None,
    openapi_url="/openapi.json" if not settings.is_production else None,
    lifespan=lifespan,
)

# ---------------------------------------------------------------------------
# Rate-limiter state (shared across workers via Redis backend)
# ---------------------------------------------------------------------------

app.state.limiter = limiter          # login endpoint limiter (IP-based)
app.state.data_limiter = data_limiter  # data endpoint limiter (user/key-based)

app.add_exception_handler(
    RateLimitExceeded,
    _rate_limit_exceeded_handler,  # type: ignore[arg-type]
)

# ---------------------------------------------------------------------------
# CORS
# Allows the browser to send credentials (HttpOnly cookies) from the
# frontend origin.  Never use allow_origins=["*"] with allow_credentials=True.
# ---------------------------------------------------------------------------

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins_list,
    allow_credentials=True,                # required for cookie exchange
    allow_methods=["GET", "POST", "PATCH", "OPTIONS"],
    allow_headers=[
        "Authorization",
        "Content-Type",
        "X-Service-Key",   # Caller Type 2 HMAC headers
        "X-Timestamp",
        "X-Signature",
    ],
    expose_headers=["Retry-After"],        # expose for 429 responses
)

# ---------------------------------------------------------------------------
# Routers
# ---------------------------------------------------------------------------

app.include_router(auth.router)        # /auth/*
app.include_router(admin.router)       # /admin/*
app.include_router(data.router)        # /data/*
app.include_router(internal.router)    # /internal/jobs/*

# ---------------------------------------------------------------------------
# Global exception handler — never leak stack traces to clients
# ---------------------------------------------------------------------------

@app.exception_handler(Exception)
async def _unhandled_exception_handler(
    request: Request,
    exc: Exception,
) -> JSONResponse:
    log = structlog.get_logger("app.errors")
    log.exception("unhandled_exception", path=request.url.path)
    return JSONResponse(
        status_code=500,
        content={"detail": "An internal error occurred"},
    )

# ---------------------------------------------------------------------------
# Liveness / readiness probes
# ---------------------------------------------------------------------------

@app.get("/health", tags=["system"], include_in_schema=False)
async def health() -> dict:
    return {"status": "ok"}


# ---------------------------------------------------------------------------
# Uvicorn entrypoint (dev only — use gunicorn + uvicorn workers in prod)
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "main:app",
        host=settings.__dict__.get("app_host", "0.0.0.0"),
        port=settings.__dict__.get("app_port", 8000),
        reload=not settings.is_production,
        log_level=settings.log_level.lower(),
    )
