from __future__ import annotations

import importlib
import sys  # noqa: F401 - retained for legacy tests and runtime diagnostics monkeypatching.
from contextlib import asynccontextmanager
from typing import Any, AsyncIterator, Literal

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

from grantflow.api.compat_exports import APP_LEGACY_EXPORT_MODULES
from grantflow.api.errors import agent_error_detail
from grantflow.api import runtime_state
from grantflow.api.routers import include_api_routers
from grantflow.api.runtime_service import _build_job_runner as _build_job_runner_impl
from grantflow.api.schemas import ExportRequest  # noqa: F401 - public compatibility export.
from grantflow.api.security import install_openapi_api_key_security
from grantflow.core.config import config
from grantflow.core.version import __version__
from grantflow.memory_bank.vector_store import vector_store  # noqa: F401 - compatibility export.

HITLStartAt = Literal["start", "architect", "mel", "critic"]

JOB_STORE = runtime_state.JOB_STORE
INGEST_AUDIT_STORE = runtime_state.INGEST_AUDIT_STORE
hitl_manager = runtime_state.hitl_manager

_LEGACY_EXPORT_MODULES = APP_LEGACY_EXPORT_MODULES

__all__ = [
    "app",
    "config",
    "ExportRequest",
    "HITLStartAt",
    "JOB_STORE",
    "INGEST_AUDIT_STORE",
    "JOB_RUNNER",
    "hitl_manager",
    "vector_store",
]


def __getattr__(name: str) -> Any:
    module_name = _LEGACY_EXPORT_MODULES.get(name)
    if module_name is None:
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

    module = importlib.import_module(module_name)
    if not hasattr(module, name):
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
    value = getattr(module, name)
    globals()[name] = value
    return value


@asynccontextmanager
async def _app_lifespan(_: FastAPI) -> AsyncIterator[None]:
    from grantflow.api.runtime_service import _app_lifespan as _impl

    async with _impl(_):
        yield


app = FastAPI(
    title="GrantFlow API",
    description="Agent-native API for governed grant-proposal workflows",
    version=__version__,
    lifespan=_app_lifespan,
)

install_openapi_api_key_security(app)


@app.exception_handler(RequestValidationError)
async def _agent_request_validation_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
    """Map FastAPI/Pydantic 422 validation errors onto the agent error contract.

    Default FastAPI returns a raw error array under ``detail``; every agent-facing
    endpoint promises ``{code, message, retryable, next_action}`` (see AGENTS.md and
    CLAUDE.md). Request-validation failures are the most common error an agent hits
    while learning the schema, so they must carry a structured ``next_action`` too.
    The original field-level errors are preserved under ``errors`` for debugging.
    """
    errors = [
        {
            "loc": list(err.get("loc", ())),
            "msg": err.get("msg", ""),
            "type": err.get("type", ""),
        }
        for err in exc.errors()
    ]
    detail = agent_error_detail(
        code="invalid_request",
        message="Request validation failed.",
        retryable=False,
        next_action=(
            "Fix the fields listed in 'errors' and retry. See sample_requests from "
            "POST /agents/register or the request schema in /openapi.json."
        ),
        errors=errors,
    )
    return JSONResponse(status_code=422, content={"detail": detail})


include_api_routers(app)

runtime_state.JOB_RUNNER = _build_job_runner_impl()
JOB_RUNNER = runtime_state.JOB_RUNNER


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host=config.api_host, port=config.api_port, reload=config.debug)
