"""Endpoints des notebooks (atelier RAG) : indexation des sources + chats ancrés."""
import logging
from typing import Annotated, AsyncIterator

from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field

from app.api.common import MAX_PDF_BYTES, sse_event
from app.api.deps import (
    get_notebook_chat_use_case,
    get_notebook_deep_use_case,
    get_notebook_rag_use_case,
)
from app.application.embeddings import EmbeddingError
from app.application.notebook_chat import NotebookChatUseCase
from app.application.notebook_deep import NotebookDeepUseCase
from app.application.notebook_rag import NotebookRagUseCase
from app.core.config import Settings, get_settings
from app.domain.models import ChatMessage
from app.domain.ports import LLMProviderError, PdfExtractionError
from app.infrastructure import vector_store

logger = logging.getLogger(__name__)

router = APIRouter()


class IndexSourceResponseDTO(BaseModel):
    chunks: int
    page_count: int
    ocr_page_count: int


@router.post("/index/notebook-source", response_model=IndexSourceResponseDTO)
async def index_notebook_source(
    rag: Annotated[NotebookRagUseCase, Depends(get_notebook_rag_use_case)],
    source_id: str = Form(...),
    file: UploadFile = File(...),
) -> IndexSourceResponseDTO:
    """Indexe une source PDF (extraction + embeddings + stockage vectoriel)."""
    content = await file.read()
    if not content:
        raise HTTPException(status_code=422, detail="Fichier PDF vide.")
    if len(content) > MAX_PDF_BYTES:
        raise HTTPException(
            status_code=413, detail=f"PDF trop volumineux (> {MAX_PDF_BYTES // (1024 * 1024)} Mo).")
    try:
        recap = await rag.index_source(source_id, content)
    except PdfExtractionError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    except EmbeddingError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    return IndexSourceResponseDTO(**recap)


@router.delete("/index/notebook-source/{source_id}")
def delete_notebook_source(source_id: str) -> dict[str, str]:
    """Supprime les vecteurs d'une source (au DELETE d'une source/notebook)."""
    vector_store.delete(source_id)
    return {"status": "deleted", "source_id": source_id}


class NotebookChatMessageDTO(BaseModel):
    role: str
    content: str


class NotebookChatRequestDTO(BaseModel):
    source_ids: list[str] = Field(default_factory=list)
    messages: list[NotebookChatMessageDTO] = Field(default_factory=list)
    context: str = Field(default="")


@router.post("/chat/notebook/stream")
async def chat_notebook_stream(
    body: NotebookChatRequestDTO,
    use_case: Annotated[NotebookChatUseCase, Depends(get_notebook_chat_use_case)],
    settings: Annotated[Settings, Depends(get_settings)],
) -> StreamingResponse:
    """Chat ANCRÉ sur les sources (RAG) : récupère les passages pertinents puis
    streame la réponse. Évènements SSE : `token` {token}, `done` {}, `error` {message}."""
    messages = [ChatMessage(role=m.role, content=m.content) for m in body.messages]
    top_k = max(1, min(settings.rag_top_k, 200))

    async def event_stream() -> AsyncIterator[str]:
        try:
            async for ev in use_case.stream(body.source_ids, messages, context=body.context, top_k=top_k):
                if ev["type"] == "token":
                    if ev.get("token"):
                        yield sse_event("token", {"token": ev["token"]})
                else:
                    # 'sources' (et tout futur évènement typé) : relayé tel quel.
                    ev_type = ev.pop("type")
                    yield sse_event(ev_type, ev)
            yield sse_event("done", {})
        except (LLMProviderError, EmbeddingError) as exc:
            yield sse_event("error", {"message": str(exc)})
        except Exception as exc:  # noqa: BLE001 — filet : pas de coupure brutale du flux.
            logger.exception("Chat notebook : erreur inattendue.")
            yield sse_event("error", {"message": f"Erreur inattendue du Brain : {type(exc).__name__} : {exc}"})

    return StreamingResponse(event_stream(), media_type="text/event-stream")


@router.post("/chat/notebook/deep/stream")
async def chat_notebook_deep_stream(
    body: NotebookChatRequestDTO,
    use_case: Annotated[NotebookDeepUseCase, Depends(get_notebook_deep_use_case)],
) -> StreamingResponse:
    """Analyse APPROFONDIE (map-reduce sur tout le document). Évènements SSE :
    `progress` {current,total} pendant la lecture, puis `token` {token}, puis `done`."""
    messages = [ChatMessage(role=m.role, content=m.content) for m in body.messages]
    question = next((m.content for m in reversed(messages) if m.role == "user"), "")

    async def event_stream() -> AsyncIterator[str]:
        if not question.strip():
            yield sse_event("error", {"message": "Question vide."})
            return
        try:
            async for ev in use_case.stream(body.source_ids, messages, context=body.context):
                ev_type = ev.pop("type")
                yield sse_event(ev_type, ev)
        except (LLMProviderError, EmbeddingError) as exc:
            yield sse_event("error", {"message": str(exc)})
        except Exception as exc:  # noqa: BLE001 — filet : pas de coupure brutale.
            logger.exception("Analyse approfondie : erreur inattendue.")
            yield sse_event("error", {"message": f"Erreur inattendue du Brain : {type(exc).__name__} : {exc}"})

    return StreamingResponse(event_stream(), media_type="text/event-stream")
