"""FastAPI application factory for Frago Web Service.

Creates and configures the FastAPI application with:
- CORS middleware for local development
- Static file serving for frontend assets
- API routers for all endpoints
- WebSocket endpoint for real-time updates
- Background session synchronization
"""

from contextlib import asynccontextmanager
from pathlib import Path
from typing import Optional

from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse

from frago.server.routes import (
    system_router,
    dashboard_router,
    recipes_router,
    tasks_router,
    agent_router,
    config_router,
    skills_router,
    settings_router,
    sync_router,
    console_router,
    init_router,
    viewer_router,
    files_router,
    workspace_router,
)
from frago.server.websocket import manager, MessageType, create_message
from frago.server.services.cache_service import CacheService
from frago.server.services.sync_service import SyncService
from frago.server.services.community_recipe_service import CommunityRecipeService
from frago.server.services.version_service import VersionCheckService


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan manager.

    Handles startup and shutdown events:
    - Initialize cache with preloaded data
    - Auto-sync official resources if enabled
    - Start background session sync
    - Start community recipe refresh service
    - Stop services on shutdown
    """
    import logging

    logger = logging.getLogger(__name__)

    # Startup: Initialize cache first (preload all data)
    cache_service = CacheService.get_instance()
    await cache_service.initialize()

    # Auto-sync official resources if enabled
    try:
        from frago.init.config_manager import load_config
        from frago.server.services.official_resource_sync_service import (
            OfficialResourceSyncService,
        )

        config = load_config()
        if config.official_resource_sync_enabled:
            logger.info("Auto-syncing official resources from GitHub...")
            OfficialResourceSyncService.start_sync()
    except Exception as e:
        logger.warning("Failed to auto-sync official resources: %s", e)

    # Start sync service and link to cache
    sync_service = SyncService.get_instance()
    sync_service.set_cache_service(cache_service)
    await sync_service.start()

    # Initialize and start community recipe service (60s refresh interval)
    community_service = CommunityRecipeService.get_instance()
    community_service.set_cache_service(cache_service)
    await community_service.initialize()  # Fetch first to populate initial data
    await community_service.start()

    # Initialize and start version check service (1h refresh interval)
    version_service = VersionCheckService.get_instance()
    await version_service.initialize()
    await version_service.start()

    yield

    # Shutdown
    await version_service.stop()
    await community_service.stop()
    await sync_service.stop()


def get_frontend_path() -> Path:
    """Get the path to frontend build assets for the web service.

    Returns:
        Path to the server frontend dist directory
    """
    # Look for built assets in server/assets (packaged) or server/web/dist (dev)
    server_dir = Path(__file__).parent

    # Check for packaged assets first
    assets_dir = server_dir / "assets"
    if assets_dir.exists() and (assets_dir / "index.html").exists():
        return assets_dir

    # Fall back to frontend build output
    frontend_dist = server_dir / "web" / "dist"
    if frontend_dist.exists():
        return frontend_dist

    # Return assets dir even if it doesn't exist (will be created during build)
    return assets_dir


def create_app(
    title: str = "Frago Web Service",
    version: Optional[str] = None,
) -> FastAPI:
    """Create and configure the FastAPI application.

    Args:
        title: Application title for OpenAPI docs
        version: Application version (defaults to frago version)

    Returns:
        Configured FastAPI application
    """
    # Get version from package if not provided
    if version is None:
        try:
            from frago import __version__

            version = __version__
        except ImportError:
            version = "0.0.0"

    app = FastAPI(
        title=title,
        version=version,
        description="Local web service for Frago GUI",
        docs_url="/api/docs",
        redoc_url="/api/redoc",
        openapi_url="/api/openapi.json",
        lifespan=lifespan,
    )

    # Configure CORS for local development
    # Allow localhost origins on common ports for security
    app.add_middleware(
        CORSMiddleware,
        allow_origins=[
            "http://localhost:8080",
            "http://127.0.0.1:8080",
            "http://localhost:8093",
            "http://127.0.0.1:8093",
            "http://localhost:3000",  # Vite dev server
            "http://127.0.0.1:3000",
        ],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    # Global error handler for consistent API error responses
    @app.exception_handler(Exception)
    async def global_exception_handler(request: Request, exc: Exception):
        """Handle all uncaught exceptions with a consistent JSON response."""
        import logging
        import traceback

        logger = logging.getLogger(__name__)
        logger.error(f"Unhandled exception: {exc}\n{traceback.format_exc()}")

        # Return JSON error for API routes, let others pass through
        if request.url.path.startswith("/api"):
            return JSONResponse(
                status_code=500,
                content={
                    "error": "Internal server error",
                    "detail": str(exc) if app.debug else "An unexpected error occurred",
                },
            )
        raise exc

    # Include API routers
    app.include_router(system_router, prefix="/api", tags=["system"])
    app.include_router(dashboard_router, prefix="/api", tags=["dashboard"])
    app.include_router(recipes_router, prefix="/api", tags=["recipes"])
    app.include_router(tasks_router, prefix="/api", tags=["tasks"])
    app.include_router(agent_router, prefix="/api", tags=["agent"])
    app.include_router(config_router, prefix="/api", tags=["config"])
    app.include_router(skills_router, prefix="/api", tags=["skills"])
    app.include_router(settings_router, prefix="/api", tags=["settings"])
    app.include_router(sync_router, prefix="/api", tags=["sync"])
    app.include_router(console_router, prefix="/api", tags=["console"])
    app.include_router(init_router, prefix="/api", tags=["init"])

    # Viewer routes for content preview (not under /api)
    app.include_router(viewer_router, prefix="/viewer", tags=["viewer"])
    app.include_router(files_router, prefix="/api", tags=["files"])
    app.include_router(workspace_router, prefix="/api", tags=["workspace"])

    # WebSocket endpoint for real-time updates
    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        """WebSocket endpoint for real-time updates.

        Clients connect here to receive:
        - Task status updates
        - Session sync events
        - Log streaming
        - Initial data push on connect
        """
        await manager.connect(websocket)
        try:
            # Send welcome message
            await manager.send_personal(
                websocket,
                create_message(
                    MessageType.CONNECTED,
                    {"message": "Connected to Frago Web Service"},
                ),
            )

            # Push initial data immediately if cache is ready
            cache_service = CacheService.get_instance()
            if cache_service.is_initialized():
                initial_data = await cache_service.get_initial_data()
                await manager.send_personal(
                    websocket,
                    create_message(MessageType.DATA_INITIAL, initial_data),
                )

            # Push version info if available
            version_service = VersionCheckService.get_instance()
            version_info = await version_service.get_version_info()
            if version_info:
                await manager.send_personal(
                    websocket,
                    create_message(MessageType.DATA_VERSION, {"data": version_info}),
                )

            # Keep connection alive and handle incoming messages
            while True:
                data = await websocket.receive_text()
                # Handle ping/pong for keepalive
                try:
                    import json
                    msg = json.loads(data)
                    if msg.get("type") == MessageType.PING:
                        await manager.send_personal(
                            websocket,
                            create_message(MessageType.PONG),
                        )
                except (json.JSONDecodeError, KeyError):
                    pass
        except WebSocketDisconnect:
            await manager.disconnect(websocket)

    # Mount static files for frontend
    frontend_path = get_frontend_path()
    if frontend_path.exists():
        app.mount(
            "/assets",
            StaticFiles(directory=str(frontend_path / "assets") if (frontend_path / "assets").exists() else str(frontend_path)),
            name="assets",
        )

    # SPA fallback: serve index.html for all non-API routes
    @app.get("/{full_path:path}")
    async def serve_spa(full_path: str):
        """Serve the SPA for all non-API routes.

        This enables client-side routing by serving index.html
        for any path that doesn't match an API route.
        """
        # Don't serve SPA for API or viewer routes (they should 404 naturally)
        if full_path.startswith("api/") or full_path.startswith("viewer/"):
            from fastapi import HTTPException

            raise HTTPException(status_code=404, detail="Endpoint not found")

        # Serve index.html for SPA routing
        index_path = frontend_path / "index.html"
        if index_path.exists():
            return FileResponse(str(index_path))

        # Return a helpful message if frontend not built
        from fastapi.responses import HTMLResponse

        return HTMLResponse(
            content="""
            <!DOCTYPE html>
            <html>
            <head><title>Frago - Frontend Not Built</title></head>
            <body style="font-family: system-ui; padding: 2rem; background: #1a1a1a; color: #fff;">
                <h1>Frontend assets not found</h1>
                <p>Please build the frontend first:</p>
                <pre style="background: #333; padding: 1rem; border-radius: 4px;">
cd src/frago/server/web
pnpm install
pnpm build
                </pre>
                <p>Then restart <code>frago server</code></p>
            </body>
            </html>
            """,
            status_code=503,
        )

    return app
