"""FastAPI application factory and entrypoint.

This module creates the FastAPI app, adds middleware, includes all feature
routers, and provides the CLI entrypoint.
"""

import os
from contextlib import asynccontextmanager

import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse as StarletteJSONResponse

from peon_mcp.db import init_db
from peon_mcp.discord.bot import bot_manager
from peon_mcp.discovery import discover_routers


# ---------------------------------------------------------------------------
# App setup
# ---------------------------------------------------------------------------


@asynccontextmanager
async def app_lifespan(application):
    """Application lifespan: create shared DB connection and start Discord bots."""
    db = await init_db()
    application.state.db = db
    application.state.bot_manager = bot_manager

    await bot_manager.start_all_enabled(db)
    yield
    await bot_manager.stop_all()
    await db.close()


class APIKeyMiddleware(BaseHTTPMiddleware):
    """Middleware that checks X-API-Key header on /api/ routes."""

    def __init__(self, application, api_key: str):
        super().__init__(application)
        self.api_key = api_key

    async def dispatch(self, request: Request, call_next):
        # Skip auth for non-API routes (static/SPA) and CORS preflight
        if not request.url.path.startswith("/api/") or request.method == "OPTIONS":
            return await call_next(request)

        provided_key = request.headers.get("X-API-Key")
        if provided_key != self.api_key:
            return StarletteJSONResponse(
                status_code=401,
                content={"error": "Invalid or missing API key"},
            )

        return await call_next(request)


def create_app() -> FastAPI:
    application = FastAPI(
        title="Peon MCP",
        version="0.6.0",
        lifespan=app_lifespan,
    )

    application.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_methods=["*"],
        allow_headers=["*"],
    )

    # Optional API key authentication
    api_key = os.environ.get("PEON_API_KEY")
    if api_key:
        application.add_middleware(APIKeyMiddleware, api_key=api_key)

    @application.exception_handler(HTTPException)
    async def http_exception_handler(request: Request, exc: HTTPException):
        """Return errors in {"error": "..."} format for backward compatibility."""
        return JSONResponse(
            status_code=exc.status_code,
            content={"error": exc.detail},
        )

    @application.exception_handler(RequestValidationError)
    async def validation_exception_handler(request: Request, exc: RequestValidationError):
        """Return validation errors in {"error": "..."} format."""
        errors = exc.errors()
        messages = []
        for err in errors:
            loc = " -> ".join(str(part) for part in err["loc"])
            messages.append(f"{loc}: {err['msg']}")
        return JSONResponse(
            status_code=422,
            content={"error": "; ".join(messages)},
        )

    # Auto-discover and include feature routers
    for router in discover_routers():
        application.include_router(router)

    return application


app = create_app()


# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------


def find_open_port(host: str, preferred: int) -> int:
    """Return *preferred* if available, otherwise ask the OS for a free port."""
    import socket

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.bind((host, preferred))
        sock.close()
        return preferred
    except OSError:
        pass

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((host, 0))
    port = sock.getsockname()[1]
    sock.close()
    return port


def main():
    host = os.environ.get("PEON_UI_HOST", "127.0.0.1")
    preferred_port = int(os.environ.get("PEON_UI_PORT", "8420"))
    port = find_open_port(host, preferred_port)
    if port != preferred_port:
        print(f"Port {preferred_port} in use, using {port} instead")
    url = f"http://{host}:{port}"
    link = f"\033]8;;{url}\033\\{url}\033]8;;\033\\"
    print(f"peon-ui starting on {link}")
    uvicorn.run(app, host=host, port=port)


if __name__ == "__main__":
    main()
