"""FastAPI application factory."""

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from pydanticai_multiagent import __version__
from pydanticai_multiagent.config import settings
from pydanticai_multiagent.mcp import (
    cleanup_mcp_clients,
    get_mcp_toolsets,
    initialize_mcp_clients,
    mcp_server,
    set_mcp_state,
)

from .middleware.auth import AuthMiddleware
from .middleware.rate_limit import RateLimitMiddleware
from .middleware.redis_rate_limit import RedisRateLimitMiddleware
from .routes import admin, agents, chat, health, platform


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    """Application lifespan handler for startup/shutdown.

    This is where you initialize and cleanup shared resources like:
    - Database connection pools
    - HTTP clients
    - Cache connections
    - Background tasks
    """
    from pydanticai_multiagent.services import AgentRegistry, UsageTracker
    from pydanticai_multiagent.services.conversation import ConversationService
    from pydanticai_multiagent.services.message_store import MessageStore

    # Startup - Database
    db_url = settings.database_url
    use_real_db = db_url and db_url.startswith("postgresql://")

    if use_real_db:
        from pydanticai_multiagent.db import create_pool

        app.state.db = await create_pool(db_url)
    else:
        from pydanticai_multiagent.dependencies.mocks import MockDatabase

        app.state.db = MockDatabase()

    # Startup - Services (same code path for real and mock)
    app.state.message_store = MessageStore(app.state.db)
    app.state.usage_tracker = UsageTracker(app.state.db)
    app.state.conversation_service = ConversationService(
        app.state.message_store,
        usage_tracker=app.state.usage_tracker,
    )

    # Startup - Redis (optional, for distributed rate limiting)
    app.state.redis = None
    if settings.redis_enabled and settings.redis_url:
        try:
            import redis.asyncio as aioredis

            app.state.redis = aioredis.from_url(
                settings.redis_url,
                encoding="utf-8",
                decode_responses=True,
            )
            await app.state.redis.ping()
        except Exception as e:
            import logging

            logging.warning(f"Redis unavailable, falling back to in-memory rate limiting: {e}")
            app.state.redis = None

    # Startup - HTTP Client (shared across all requests for connection pooling)
    import httpx

    from pydanticai_multiagent.dependencies.clients import HttpxHttpClient

    _raw_http_client = httpx.AsyncClient(timeout=30.0)
    app.state.http_client = HttpxHttpClient(_raw_http_client)
    app.state._raw_http_client = _raw_http_client  # Keep reference for cleanup

    # Startup - Cache Client (uses Redis if available, else mock)
    if app.state.redis:
        from pydanticai_multiagent.dependencies.clients import RedisCacheClient

        app.state.cache = RedisCacheClient(app.state.redis)
    else:
        from pydanticai_multiagent.dependencies.mocks import MockCache

        app.state.cache = MockCache()

    # Startup - Agent Registry with built-in agents
    from pydanticai_multiagent import (
        analyst_agent,
        code_agent,
        research_agent,
        router_agent,
        support_agent,
        writer_agent,
    )

    registry = AgentRegistry(app.state.db)
    registry.register_builtin("router", router_agent, "Routes requests to specialists")
    registry.register_builtin("research", research_agent, "Web research and information gathering")
    registry.register_builtin("analyst", analyst_agent, "Data analysis and insights")
    registry.register_builtin("code", code_agent, "Code generation and review")
    registry.register_builtin("writer", writer_agent, "Content writing and editing")
    registry.register_builtin("support", support_agent, "Customer support assistance")
    await registry.sync_builtin_agents_to_db()
    app.state.agent_registry = registry

    # Startup - MCP Client connections (external MCP servers as toolsets)
    if settings.mcp_servers:
        import logging

        logging.info(f"Initializing {len(settings.mcp_servers)} MCP server connection(s)...")
        await initialize_mcp_clients(settings.mcp_servers)
        app.state.mcp_toolsets = get_mcp_toolsets()
        logging.info(f"MCP toolsets available: {len(app.state.mcp_toolsets)}")
    else:
        app.state.mcp_toolsets = []

    # Set MCP server state so it can access platform services (after toolsets are initialized)
    set_mcp_state(
        db=app.state.db,
        conversation_service=app.state.conversation_service,
        agent_registry=registry,
        usage_tracker=app.state.usage_tracker,
        http_client=app.state.http_client,
        cache=app.state.cache,
        mcp_toolsets=app.state.mcp_toolsets,
    )

    yield

    # Shutdown
    if use_real_db and hasattr(app.state, "db"):
        await app.state.db.close()

    # Shutdown Redis
    if app.state.redis:
        await app.state.redis.close()

    # Shutdown HTTP client
    if hasattr(app.state, "_raw_http_client"):
        await app.state._raw_http_client.aclose()

    # Shutdown MCP client connections
    await cleanup_mcp_clients()


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

    Returns:
        Configured FastAPI application instance.
    """
    app = FastAPI(
        title="PydanticAI Multi-Agent API",
        description="Production-ready multi-agent framework built on PydanticAI",
        version=__version__,
        lifespan=lifespan,
    )

    # Configure CORS
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],  # Configure appropriately for production
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    # Middleware registration order matters: Starlette executes in REVERSE order.
    # We want: Request -> Auth (sets tenant) -> RateLimit (uses tenant) -> Handler
    # So register RateLimit first, then Auth.

    # Add rate limiting middleware (executes SECOND - after auth sets tenant)
    # Use Redis-based rate limiter if enabled, otherwise in-memory
    # Note: /mcp is excluded because MCP handles its own rate limiting per-tenant
    rate_limit_middleware = (
        RedisRateLimitMiddleware if settings.redis_enabled else RateLimitMiddleware
    )
    app.add_middleware(
        rate_limit_middleware,
        requests_per_minute=settings.rate_limit_requests_per_minute,
        burst_size=settings.rate_limit_burst_size,
        exclude_paths=["/health", "/ready", "/mcp"],
    )

    # Add authentication middleware (executes FIRST - sets request.state.tenant)
    # Note: /mcp is excluded because MCP handles its own auth via API key in tool params
    app.add_middleware(
        AuthMiddleware,
        secret_key=settings.secret_key,
        exclude_paths=["/health", "/ready", "/docs", "/openapi.json", "/redoc", "/mcp"],
    )

    # Include routers
    app.include_router(health.router, tags=["Health"])
    app.include_router(chat.router, prefix="/api/v1", tags=["Chat"])
    app.include_router(agents.router, prefix="/api/v1", tags=["Agents"])
    app.include_router(platform.router, prefix="/api/v1", tags=["Platform"])
    app.include_router(admin.router, prefix="/api/v1", tags=["Admin"])

    # Mount MCP server at /mcp for MCP clients (Claude Desktop, Cursor, etc.)
    mcp_app = mcp_server.http_app(path="/mcp")
    app.mount("/mcp", mcp_app)

    return app


# Create app instance for uvicorn
app = create_app()
