import json
import uuid
from collections.abc import AsyncGenerator

from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from langchain_core.messages import HumanMessage

from app.agent.graph import get_graph_for_tenant, load_tenant
from app.db.models import Tenant
from app.schemas.chat import ChatRequest

router = APIRouter()

# Tools whose structured return value (via response_format="content_and_artifact") is
# worth rendering as a rich card carousel in the widget, keyed by the "kind" the frontend expects.
_CARD_TOOL_KIND = {
    "search_doctors": "doctors",
    "search_properties": "properties",
    "search_packages": "packages",
}


def _sse(event: str, data: dict) -> str:
    return f"event: {event}\ndata: {json.dumps(data)}\n\n"


async def _stream(tenant: Tenant, session_id: str, message: str) -> AsyncGenerator[str, None]:
    graph = await get_graph_for_tenant(tenant)
    config = {"configurable": {"thread_id": session_id}}
    full_text = ""
    try:
        async for event in graph.astream_events(
            {"messages": [HumanMessage(content=message)]}, config=config, version="v2"
        ):
            kind = event["event"]
            if kind == "on_chat_model_stream" and event.get("metadata", {}).get("langgraph_node") == "agent":
                content = event["data"]["chunk"].content
                if content:
                    full_text += content
                    yield _sse("token", {"content": content})
            elif kind == "on_tool_end":
                card_kind = _CARD_TOOL_KIND.get(event.get("name", ""))
                artifact = getattr(event["data"].get("output"), "artifact", None)
                if card_kind and artifact:
                    yield _sse("cards", {"kind": card_kind, "items": artifact})
        yield _sse("done", {"content": full_text, "session_id": session_id})
    except Exception as exc:  # surfaced to the client instead of a broken stream
        yield _sse("error", {"message": str(exc)})


@router.post("/chat/{tenant_id}")
async def chat(tenant_id: str, body: ChatRequest) -> StreamingResponse:
    tenant = load_tenant(tenant_id)
    if tenant is None:
        raise HTTPException(status_code=404, detail="Unknown tenant")
    if not body.message.strip():
        raise HTTPException(status_code=400, detail="message must not be empty")

    session_id = body.session_id or str(uuid.uuid4())
    return StreamingResponse(
        _stream(tenant, session_id, body.message),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Session-Id": session_id},
    )
