from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.exceptions import ResponseValidationError
from fastapi.responses import JSONResponse

from .config import settings
from .exceptions import LLMGenerationError
from .schemas import (
    GenerateItineraryRequest,
    GenerateItineraryResponse,
    RecommendStayRequest,
    RecommendStayResponse,
    SuggestQuestionsRequest,
    SuggestQuestionsResponse,
    SuggestResponseRequest,
    SuggestResponseResponse,
)
from .services import itinerary, questions, reply, stay

app = FastAPI(title="meridian-ai", version="0.1.0")


def require_service_token(authorization: str | None = Header(default=None)) -> None:
    """Baseline service-to-service auth. Meridian-Backend authenticates with a shared
    bearer token - never the end user's own frontend session token, which this
    service never sees and has no way to validate (see README.md 4.1 for why that
    split matters). This is the simple half of that section's design; swap the token
    comparison below for the signed-request-with-nonce scheme described there if a
    static shared secret ever stops being a strong enough guarantee.
    """
    if not settings.service_token:
        return  # auth disabled - fine for local dev, never leave unset in a real deployment
    if authorization != f"Bearer {settings.service_token}":
        raise HTTPException(status_code=401, detail="Invalid or missing service token")


@app.exception_handler(LLMGenerationError)
def handle_llm_generation_error(_request: Request, exc: LLMGenerationError) -> JSONResponse:
    return JSONResponse(
        status_code=502,
        content={"error": {"code": "LLM_GENERATION_FAILED", "message": exc.message, "details": exc.detail}},
    )


@app.exception_handler(ResponseValidationError)
def handle_response_validation_error(_request: Request, exc: ResponseValidationError) -> JSONResponse:
    # The model's JSON parsed fine but didn't match the response schema (FastAPI raises
    # this - not pydantic.ValidationError directly - when response_model validation
    # fails). Surfaced as a distinct error code from a hard provider failure so callers
    # can tell "the model said something odd" apart from "the provider/network failed".
    return JSONResponse(
        status_code=502,
        content={"error": {"code": "LLM_RESPONSE_INVALID", "message": "Model output did not match the expected shape", "details": str(exc)}},
    )


@app.get("/models")
def models() -> dict:
    """Returns available LLM providers and their models. No auth required — the
    frontend reads this to populate the model selector in the generate-itinerary modal.
    """
    from .providers.factory import list_providers
    return {"providers": list_providers(), "default": settings.llm_provider}


@app.get("/health")
def health() -> dict:
    from .providers.factory import get_provider
    p = get_provider()
    return {
        "status": "ok",
        "primary_provider": settings.llm_provider,
        "active_providers": p.active,
        "exhausted_providers": p.exhausted,
    }


@app.get("/providers/status", dependencies=[Depends(require_service_token)])
def providers_status() -> dict:
    """Shows which providers are active and which have been marked exhausted."""
    from .providers.factory import get_provider
    p = get_provider()
    return {
        "configured": p.names,
        "active": p.active,
        "exhausted": p.exhausted,
    }


@app.post("/providers/reset", dependencies=[Depends(require_service_token)])
def providers_reset(name: str | None = None) -> dict:
    """Clears exhausted state so a topped-up provider is tried again.
    Pass ?name=openai to reset just one, or call with no param to reset all.
    """
    from .providers.factory import get_provider
    p = get_provider()
    p.reset(name)
    return {"reset": name or "all", "active": p.active}


@app.post(
    "/generate_itinerary",
    response_model=GenerateItineraryResponse,
    dependencies=[Depends(require_service_token)],
)
def generate_itinerary_endpoint(payload: GenerateItineraryRequest) -> dict:
    return itinerary.generate_itinerary(payload)


@app.post(
    "/suggest_response",
    response_model=SuggestResponseResponse,
    dependencies=[Depends(require_service_token)],
)
def suggest_response_endpoint(payload: SuggestResponseRequest) -> dict:
    return reply.suggest_response(payload)


@app.post(
    "/recommend_stay",
    response_model=RecommendStayResponse,
    dependencies=[Depends(require_service_token)],
)
def recommend_stay_endpoint(payload: RecommendStayRequest) -> dict:
    return stay.recommend_stay(payload)


@app.post(
    "/suggest_questions",
    response_model=SuggestQuestionsResponse,
    dependencies=[Depends(require_service_token)],
)
def suggest_questions_endpoint(payload: SuggestQuestionsRequest) -> dict:
    return questions.suggest_questions(payload)
