"""Reasoning API — HTTP layer over a `ReasoningRunner` port.

`POST /api/documents/:id/reasoning` invokes the wired-up `ReasoningRunner`
against the stored `DoclingDocument` and returns a `ReasoningResultResponse`
in the same shape the v1 import dialog already consumes — so the frontend
overlay code is fully reused.

This module has zero coupling to docling-agent / mellea / docling-core. The
runner (concrete adapter in `infra/docling_agent_reasoning.py`) is set on
`app.state.reasoning_runner` at boot when `REASONING_ENABLED=true` and the
deps are importable. Otherwise it stays `None` and we 503.

Sync blocking call offloaded to a thread by the adapter so we don't stall
the event loop. No streaming at this step (see design doc §7 for v2 SSE plan).
"""

from __future__ import annotations

import logging

from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel

from domain.ports import ReasoningParseError, ReasoningRunner

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/documents", tags=["reasoning"])


class ReasoningRunRequest(BaseModel):
    query: str
    # Optional per-run override; falls back to the runner's default model.
    model_id: str | None = None


class ReasoningIterationResponse(BaseModel):
    iteration: int
    section_ref: str
    reason: str
    section_text_length: int
    can_answer: bool
    response: str


class ReasoningResultResponse(BaseModel):
    answer: str
    iterations: list[ReasoningIterationResponse]
    converged: bool


@router.post("/{doc_id}/reasoning", response_model=ReasoningResultResponse)
async def run_reasoning(
    doc_id: str, body: ReasoningRunRequest, request: Request
) -> ReasoningResultResponse:
    runner: ReasoningRunner | None = getattr(request.app.state, "reasoning_runner", None)
    if runner is None or not runner.is_available:
        raise HTTPException(
            status_code=503,
            detail=(
                "Live reasoning disabled (REASONING_ENABLED=false or docling-agent not installed)"
            ),
        )

    if not body.query.strip():
        raise HTTPException(status_code=400, detail="Query must not be empty")

    analysis_repo = getattr(request.app.state, "analysis_repo", None)
    if analysis_repo is None:
        raise HTTPException(status_code=500, detail="AnalysisRepository not wired")

    latest = await analysis_repo.find_latest_completed_by_document(doc_id)
    if latest is None or not latest.document_json:
        raise HTTPException(
            status_code=404,
            detail=f"No completed analysis with document_json for {doc_id}",
        )

    try:
        result = await runner.run(
            document_json=latest.document_json,
            query=body.query,
            model_id=body.model_id,
        )
    except ReasoningParseError as e:
        # The upstream LLM couldn't produce a parseable answer after retries.
        # 502 Bad Gateway — not our fault — with guidance the UI can show.
        raise HTTPException(
            status_code=502,
            detail=(
                f"The model '{e.model_id}' couldn't produce a parseable "
                "answer after retries. Try a different model (e.g. "
                "mistral-small3.2) or rephrase the question."
            ),
        ) from e
    except Exception as e:
        logger.exception("Reasoning loop failed for doc %s", doc_id)
        raise HTTPException(status_code=500, detail=f"Reasoning loop failed: {e}") from e

    return ReasoningResultResponse(
        answer=result.answer,
        iterations=[
            ReasoningIterationResponse(
                iteration=it.iteration,
                section_ref=it.section_ref,
                reason=it.reason,
                section_text_length=it.section_text_length,
                can_answer=it.can_answer,
                response=it.response,
            )
            for it in result.iterations
        ],
        converged=result.converged,
    )
