import asyncio
import logging
import re
from contextlib import asynccontextmanager
from urllib.parse import urlparse
from uuid import uuid4

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

from .agents import router as agents_router
from .auth import router as auth_router
from .computer import router as computer_router
from .computers import router as computers_router
from .config import get_settings
from .errors import AppError
from .executions import router as executions_router
from .health import router as health_router
from .maintenance import maintenance_loop
from .model_profiles import router as model_profiles_router

logger = logging.getLogger("hayva.api")


@asynccontextmanager
async def lifespan(_app: FastAPI):
    settings = get_settings()
    settings.require_app_secret()
    settings.require_production_configuration()
    stop_event = asyncio.Event()
    task = None
    if settings.app_env == "production":
        task = asyncio.create_task(maintenance_loop(stop_event))
    try:
        yield
    finally:
        if task:
            stop_event.set()
            try:
                await asyncio.wait_for(task, timeout=5)
            except TimeoutError:
                task.cancel()


app = FastAPI(title="Hayva Core API", version="0.1.0", lifespan=lifespan)
app.include_router(auth_router)
app.include_router(health_router)
app.include_router(executions_router)
app.include_router(agents_router)
app.include_router(model_profiles_router)
app.include_router(computer_router)
app.include_router(computers_router)
REQUEST_ID_PATTERN = re.compile(r"^[A-Za-z0-9._:-]{1,80}$")


@app.middleware("http")
async def request_context(request: Request, call_next):
    candidate = request.headers.get("x-request-id", "")
    request_id = candidate if REQUEST_ID_PATTERN.fullmatch(candidate) else str(uuid4())
    request.state.request_id = request_id
    settings = get_settings()
    expected_host = urlparse(settings.app_base_url).hostname
    actual_host = request.url.hostname
    internal_health = request.url.path == "/health" or request.url.path.startswith("/health/")
    loopback_health = internal_health and actual_host in {"127.0.0.1", "::1", "localhost"}
    if (
        settings.app_env == "production"
        and expected_host
        and actual_host != expected_host
        and not loopback_health
    ):
        response = JSONResponse(
            status_code=400,
            content={
                "success": False,
                "error": {
                    "code": "INVALID_HOST",
                    "message": "The request host is not allowed.",
                    "request_id": request_id,
                },
            },
        )
    else:
        response = await call_next(request)
    response.headers["x-request-id"] = request_id
    response.headers["x-content-type-options"] = "nosniff"
    response.headers["referrer-policy"] = "no-referrer"
    response.headers["permissions-policy"] = "camera=(), microphone=(), geolocation=()"
    if settings.app_env == "production":
        response.headers["strict-transport-security"] = "max-age=63072000; includeSubDomains"
    return response


@app.exception_handler(AppError)
async def app_error_handler(request: Request, error: AppError):
    return JSONResponse(status_code=error.status_code, content={"success": False, "error": {
        "code": error.code, "message": error.message, "request_id": request.state.request_id}})


@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, _error: RequestValidationError):
    return JSONResponse(status_code=422, content={"success": False, "error": {
        "code": "VALIDATION_ERROR", "message": "The request payload is invalid.",
        "request_id": request.state.request_id}})


@app.exception_handler(Exception)
async def unhandled_error_handler(request: Request, error: Exception):
    request_id = getattr(request.state, "request_id", "unavailable")
    logger.exception("unhandled_request_error request_id=%s", request_id, exc_info=error)
    return JSONResponse(status_code=500, content={"success": False, "error": {
        "code": "INTERNAL_ERROR", "message": "The request could not be completed.",
        "request_id": request_id}})


@app.get("/health", tags=["health"])
async def health() -> dict[str, str]:
    return {"status": "ok", "service": "core-api"}
