import asyncio
import logging
import os
from contextlib import asynccontextmanager

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import PlainTextResponse

_log = logging.getLogger("nextpy_server")


@asynccontextmanager
async def _lifespan(app: FastAPI):
    watcher_task = None
    if os.environ.get("N6K_DEV") == "1":
        from nextpy_server.dev import start_watcher, startup_rebuild

        await startup_rebuild(app)
        watcher_task = asyncio.create_task(start_watcher())

    yield

    if watcher_task:
        watcher_task.cancel()


DEFAULT_ASSET_PREFIX = "/_nextpy"


def create_app(*, asset_prefix: str = DEFAULT_ASSET_PREFIX) -> FastAPI:
    level = logging.DEBUG if os.environ.get("N6K_DEBUG") == "1" else logging.INFO
    logger = logging.getLogger("nextpy_server")
    logger.setLevel(level)
    if not logger.handlers:
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter("%(levelname)s:     %(message)s"))
        logger.addHandler(handler)

    application = FastAPI(lifespan=_lifespan)
    application.state.asset_prefix = asset_prefix
    application.state.bindings = {}
    application.state.actions = {}
    application.state.pages = {}

    application.add_middleware(
        CORSMiddleware,
        allow_origin_regex=r".*",
        allow_methods=["*"],
        allow_headers=["*"],
        allow_credentials=True,
    )

    @application.exception_handler(StarletteHTTPException)
    async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> PlainTextResponse:
        return PlainTextResponse(exc.detail, status_code=exc.status_code)

    @application.exception_handler(Exception)
    async def plain_text_exception_handler(request: Request, exc: Exception) -> PlainTextResponse:
        status = getattr(exc, "status_code", 500)
        return PlainTextResponse(str(exc), status_code=status)

    @application.middleware("http")
    async def request_log(request: Request, call_next):
        response = await call_next(request)
        _log.info("%s %s %d", request.method, request.url.path, response.status_code)
        return response

    return application
