#!/usr/bin/env python3

"""
FastAPI application factory for the Krawl honeypot.
Replaces the old http.server-based server.py.
"""

import sys
import os
from contextlib import asynccontextmanager

from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles

from config import get_config
from tracker import AccessTracker, set_tracker
from database import initialize_database
from tasks_master import get_tasksmaster
from logger import initialize_logging, get_app_logger
from generators import random_server_header


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application startup and shutdown lifecycle."""
    config = get_config()

    # Initialize logging
    initialize_logging()
    app_logger = get_app_logger()

    # Initialize database and run pending migrations before accepting traffic
    try:
        app_logger.info(f"Initializing database at: {config.database_path}")
        initialize_database(config.database_path)
        app_logger.info("Database ready")
    except Exception as e:
        app_logger.warning(
            f"Database initialization failed: {e}. Continuing with in-memory only."
        )

    # Initialize tracker
    tracker = AccessTracker(config.max_pages_limit, config.ban_duration_seconds)
    set_tracker(tracker)

    # Store in app.state for dependency injection
    app.state.config = config
    app.state.tracker = tracker

    # Load webpages file if provided via env var
    webpages = None
    webpages_file = os.environ.get("KRAWL_WEBPAGES_FILE")
    if webpages_file:
        try:
            with open(webpages_file, "r") as f:
                webpages = f.readlines()
            if not webpages:
                app_logger.warning(
                    "The webpages file was empty. Using randomly generated links."
                )
                webpages = None
        except IOError:
            app_logger.warning(
                "Can't read webpages file. Using randomly generated links."
            )
    app.state.webpages = webpages

    # Initialize canary counter
    app.state.counter = config.canary_token_tries

    # Start scheduled tasks
    tasks_master = get_tasksmaster()
    tasks_master.run_scheduled_tasks()

    banner = f"""

============================================================
DASHBOARD AVAILABLE AT
{config.dashboard_secret_path}
============================================================
    """
    app_logger.info(banner)
    app_logger.info(f"Starting deception server on port {config.port}...")
    if config.canary_token_url:
        app_logger.info(
            f"Canary token will appear after {config.canary_token_tries} tries"
        )
    else:
        app_logger.info("No canary token configured (set CANARY_TOKEN_URL to enable)")

    yield

    # Shutdown
    app_logger.info("Server shutting down...")


def create_app() -> FastAPI:
    """Create and configure the FastAPI application."""
    application = FastAPI(
        docs_url=None,
        redoc_url=None,
        openapi_url=None,
        lifespan=lifespan,
    )

    # Random server header middleware (innermost — runs last on request, first on response)
    @application.middleware("http")
    async def server_header_middleware(request: Request, call_next):
        response: Response = await call_next(request)
        response.headers["Server"] = random_server_header()
        return response

    # Deception detection middleware (path traversal, XXE, command injection)
    from middleware.deception import DeceptionMiddleware

    application.add_middleware(DeceptionMiddleware)

    # Banned IP check middleware (outermost — runs first on request)
    from middleware.ban_check import BanCheckMiddleware

    application.add_middleware(BanCheckMiddleware)

    # Mount static files for the dashboard
    config = get_config()
    secret = config.dashboard_secret_path.lstrip("/")
    static_dir = os.path.join(os.path.dirname(__file__), "templates", "static")
    application.mount(
        f"/{secret}/static",
        StaticFiles(directory=static_dir),
        name="dashboard-static",
    )

    # Import and include routers
    from routes.honeypot import router as honeypot_router
    from routes.api import router as api_router
    from routes.dashboard import router as dashboard_router
    from routes.htmx import router as htmx_router

    # Dashboard/API/HTMX routes (prefixed with secret path, before honeypot catch-all)
    dashboard_prefix = f"/{secret}"
    application.include_router(dashboard_router, prefix=dashboard_prefix)
    application.include_router(api_router, prefix=dashboard_prefix)
    application.include_router(htmx_router, prefix=dashboard_prefix)

    # Honeypot routes (catch-all must be last)
    application.include_router(honeypot_router)

    return application


app = create_app()
