from datetime import date, datetime
from decimal import Decimal
from typing import Any, Optional

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field

from app.db import close_pool, execute_returning, fetch_all, fetch_one, init_pool
from app.middleware import log_agent_event
from app.packaging_routes import router as packaging_router
from app.retail import router as retail_router
from app.retail_360_routes import router as retail_360_router
from app.research import router as research_router
from app.recommendations import router as recommendations_router
from app.ops_routes import router as ops_router
from app.logging_middleware import AgentLoggingMiddleware
from app import brain as brain_svc


class BrainMessageIn(BaseModel):
    chat_id: int
    direction: str = Field(..., pattern="^(in|out)$")
    content_text: Optional[str] = None
    content_type: str = "text"
    role: str = "user"
    telegram_message_id: Optional[int] = None
    reply_to_db_id: Optional[int] = None
    agent_name: Optional[str] = None
    content_json: dict[str, Any] = Field(default_factory=dict)
    user_name: Optional[str] = None
    user_role: Optional[str] = None
    chat_type: str = "private"
    embed: bool = True


class BrainEdgeIn(BaseModel):
    source_message_id: int
    edge_type: str
    target_message_id: Optional[int] = None
    target_entity_type: Optional[str] = None
    target_entity_id: Optional[int] = None
    weight: float = 1.0
    metadata: dict[str, Any] = Field(default_factory=dict)


class BrainSearchIn(BaseModel):
    query: str = Field(..., min_length=2)
    chat_id: Optional[int] = None
    limit: int = Field(default=8, ge=1, le=30)


from app.export_intel import router as export_intel_router
from app.email_config import get_active_email_config
from app.comfyui import fetch_image_bytes, generate_image, get_job, start_generation, QUALITY_PRESETS
import json
import os

DOC_INGEST_URL = os.getenv("DOC_INGEST_URL", "http://10.4.7.19:8750")

app = FastAPI(title="Foodlinkk Tools API", version="1.1.0")
app.add_middleware(AgentLoggingMiddleware)
app.include_router(retail_router)
app.include_router(retail_360_router)
app.include_router(research_router)
app.include_router(recommendations_router)
app.include_router(packaging_router)
app.include_router(ops_router)
app.include_router(export_intel_router)


class AgentEventCreate(BaseModel):
    agent_name: str = Field(..., max_length=64)
    event_type: str = Field(..., max_length=64)
    title: str = Field(..., max_length=255)
    body: Optional[str] = None
    agent_type: str = Field(default="openswarm", max_length=32)
    metadata: dict[str, Any] = Field(default_factory=dict)
    status: str = Field(default="completed", max_length=32)
    related_table: Optional[str] = Field(default=None, max_length=64)
    related_id: Optional[int] = None
    channel: str = Field(default="dashboard", max_length=32)


def _serialize_row(row: dict[str, Any]) -> dict[str, Any]:
    out: dict[str, Any] = {}
    for key, value in row.items():
        if isinstance(value, (datetime, date)):
            out[key] = value.isoformat()
        elif isinstance(value, Decimal):
            out[key] = float(value)
        else:
            out[key] = value
    return out


@app.on_event("startup")
def on_startup() -> None:
    init_pool()


@app.on_event("shutdown")
def on_shutdown() -> None:
    close_pool()


@app.get("/health")
def health() -> dict[str, str]:
    try:
        fetch_one("SELECT 1 AS ok")
        return {"status": "ok", "database": "connected"}
    except Exception as exc:
        return {"status": "degraded", "database": str(exc)}


@app.post("/events")
def create_event(payload: AgentEventCreate) -> dict[str, Any]:
    try:
        row = log_agent_event(**payload.model_dump())
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return _serialize_row(row)


@app.get("/events")
def list_events(
    limit: int = Query(default=50, ge=1, le=200),
    status: Optional[str] = Query(default=None),
    agent_name: Optional[str] = Query(default=None),
) -> dict[str, Any]:
    clauses: list[str] = []
    params: list[Any] = []
    if status:
        clauses.append("status = %s")
        params.append(status)
    if agent_name:
        clauses.append("agent_name = %s")
        params.append(agent_name)
    where = f"WHERE { AND .join(clauses)}" if clauses else ""
    params.append(limit)
    try:
        rows = fetch_all(
            f"""
            SELECT *
            FROM agent_events
            {where}
            ORDER BY created_at DESC
            LIMIT %s
            """,
            tuple(params),
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return {"events": [_serialize_row(r) for r in rows]}


@app.post("/events/{event_id}/approve")
def approve_event(event_id: int) -> dict[str, Any]:
    row = execute_returning(
        """
        UPDATE agent_events
        SET status = %s, completed_at = NOW()
        WHERE id = %s
        RETURNING *
        """,
        ("approved", event_id),
    )
    if row is None:
        raise HTTPException(status_code=404, detail="Event not found")
    return _serialize_row(row)


@app.post("/events/{event_id}/reject")
def reject_event(event_id: int) -> dict[str, Any]:
    row = execute_returning(
        """
        UPDATE agent_events
        SET status = %s, completed_at = NOW()
        WHERE id = %s
        RETURNING *
        """,
        ("rejected", event_id),
    )
    if row is None:
        raise HTTPException(status_code=404, detail="Event not found")
    return _serialize_row(row)

class BrowserTask(BaseModel):
    url: str
    task: str = Field(default="open")
    metadata: dict[str, Any] = Field(default_factory=dict)


class HermanDelegate(BaseModel):
    message: str
    target_agent: Optional[str] = Field(default=None, max_length=64)


@app.post("/browser/task")
async def browser_task(payload: BrowserTask) -> dict[str, Any]:
    import httpx
    import os
    browser_url = os.getenv("BROWSER_USE_URL", "http://browser-agent:7790")
    result = {"ok": True, "browser_url": browser_url, "url": payload.url}
    try:
        async with httpx.AsyncClient(timeout=30.0) as client:
            resp = await client.post(f"{browser_url.rstrip('/')}/task", json=payload.model_dump())
            result["upstream_status"] = resp.status_code
            if resp.status_code < 500:
                try:
                    result["upstream"] = resp.json()
                except Exception:
                    result["upstream"] = resp.text[:500]
    except Exception as exc:
        result["upstream_error"] = str(exc)
    try:
        log_agent_event(
            agent_name="browser",
            event_type="browser_task",
            title=f"Browser task: {payload.url[:120]}",
            body=payload.task,
            metadata={"url": payload.url, **payload.metadata, **result},
            channel="tools-api",
        )
    except Exception:
        pass
    return result


@app.post("/herman/delegate")
def herman_delegate(payload: HermanDelegate) -> dict[str, Any]:
    agent = payload.target_agent or "herman"
    try:
        row = log_agent_event(
            agent_name=agent,
            agent_type="herman_delegate",
            event_type="delegate",
            title="Herman delegation",
            body=payload.message,
            metadata={"target_agent": agent},
            channel="herman",
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return _serialize_row(row)


@app.get("/knowledge/search")
async def knowledge_search(q: str = Query(..., min_length=1), limit: int = Query(default=10, ge=1, le=50)) -> dict[str, Any]:
    import httpx
    try:
        async with httpx.AsyncClient(timeout=30.0) as client:
            r = await client.get(f"{DOC_INGEST_URL.rstrip('/')}/search", params={"q": q, "limit": limit})
            if r.status_code == 200:
                data = r.json()
                return {"query": q, "results": data.get("results", []), "source": "chroma"}
    except Exception:
        pass
    try:
        rows = fetch_all(
            """
            SELECT id, title, source, doc_type, metadata, created_at
            FROM knowledge_documents
            WHERE title ILIKE %s OR source ILIKE %s
            ORDER BY updated_at DESC NULLS LAST
            LIMIT %s
            """,
            (f"%{q}%", f"%{q}%", limit),
        )
    except Exception:
        rows = []
    return {"query": q, "results": [_serialize_row(r) for r in rows], "source": "postgres"}


@app.post("/knowledge/ingest")
async def knowledge_ingest(force: bool = Query(default=False)) -> dict[str, Any]:
    import httpx
    try:
        async with httpx.AsyncClient(timeout=600.0) as client:
            r = await client.post(f"{DOC_INGEST_URL.rstrip('/')}/ingest/scan", params={"force": force})
            r.raise_for_status()
            return r.json()
    except Exception as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc


@app.get("/knowledge/nas")
async def knowledge_nas(limit: int = Query(default=50, ge=1, le=500)) -> dict[str, Any]:
    import httpx
    try:
        async with httpx.AsyncClient(timeout=30.0) as client:
            r = await client.get(f"{DOC_INGEST_URL.rstrip('/')}/nas/list", params={"limit": limit})
            r.raise_for_status()
            return r.json()
    except Exception as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc

# --- CRM / Email / LLM memory (2nd brain) ---

class EmailSend(BaseModel):
    to: list[str] = Field(..., min_length=1)
    subject: str = Field(..., max_length=500)
    body: str
    client_id: Optional[int] = None
    deal_id: Optional[int] = None
    cc: list[str] = Field(default_factory=list)


class LlmMemoryCreate(BaseModel):
    category: str = Field(default="fact", max_length=64)
    subject: Optional[str] = Field(default=None, max_length=255)
    content: str
    client_id: Optional[int] = None
    deal_id: Optional[int] = None
    source: str = Field(default="herman", max_length=64)
    metadata: dict[str, Any] = Field(default_factory=dict)


def _crm_safe_all(sql: str, params: tuple = ()) -> list[dict[str, Any]]:
    try:
        return fetch_all(sql, params or None)
    except Exception:
        return []


@app.get("/crm/context")
def crm_context(days: int = Query(default=7, ge=1, le=30)) -> dict[str, Any]:
    """Live PostgreSQL bundle for Herman / morning briefing."""
    pipeline = fetch_one(
        "SELECT COALESCE(SUM(value), 0) AS total, COUNT(*) AS cnt FROM deals WHERE stage NOT IN ('won', 'lost')"
    )
    clients = _crm_safe_all(
        """SELECT id, name, contact, email, stage, sector, notes, mrr_estimate
           FROM clients ORDER BY updated_at DESC NULLS LAST, created_at DESC LIMIT 25"""
    )
    deals = _crm_safe_all(
        """
        SELECT d.id, d.title, d.value, d.stage, d.next_action, d.deadline, d.agent_owner,
               c.name AS client_name, c.email AS client_email
        FROM deals d
        LEFT JOIN clients c ON c.id = d.client_id
        WHERE d.stage NOT IN ('won', 'lost')
        ORDER BY d.deadline ASC NULLS LAST, d.updated_at DESC NULLS LAST
        LIMIT 20
        """
    )
    upcoming = _crm_safe_all(
        """
        SELECT d.id, d.title, d.value, d.stage, d.deadline, d.next_action, c.name AS client_name
        FROM deals d
        LEFT JOIN clients c ON c.id = d.client_id
        WHERE d.deadline IS NOT NULL
          AND d.deadline <= CURRENT_DATE + make_interval(days => %s)
          AND d.stage NOT IN ('won', 'lost')
        ORDER BY d.deadline ASC
        LIMIT 15
        """,
        (days,),
    )
    calendar = _crm_safe_all(
        """
        SELECT ce.id, ce.title, ce.starts_at, ce.ends_at, ce.location, ce.source,
               c.name AS client_name, d.title AS deal_title
        FROM calendar_events ce
        LEFT JOIN clients c ON c.id = ce.client_id
        LEFT JOIN deals d ON d.id = ce.deal_id
        WHERE ce.starts_at >= NOW() - INTERVAL '1 day'
          AND ce.starts_at <= NOW() + make_interval(days => %s)
        ORDER BY ce.starts_at ASC
        LIMIT 25
        """,
        (days,),
    )
    pending = _crm_safe_all(
        """
        SELECT id, agent_name, title, event_type, created_at
        FROM agent_events WHERE status = 'needs_approval'
        ORDER BY created_at ASC LIMIT 10
        """
    )
    emails_recent = _crm_safe_all(
        """
        SELECT id, from_addr, subject, received_at, sent_at, direction, is_read, client_id
        FROM emails ORDER BY COALESCE(received_at, sent_at) DESC NULLS LAST LIMIT 15
        """
    )
    memories = _crm_safe_all(
        """
        SELECT id, category, subject, content, client_id, deal_id, source, updated_at
        FROM llm_memory ORDER BY updated_at DESC LIMIT 20
        """
    )
    return {
        "generated_at": datetime.utcnow().isoformat() + "Z",
        "pipeline_eur": float(pipeline["total"]) if pipeline else 0.0,
        "active_deals": int(pipeline["cnt"]) if pipeline else 0,
        "clients_count": len(clients),
        "clients": [_serialize_row(c) for c in clients],
        "deals": [_serialize_row(d) for d in deals],
        "upcoming_deadlines": [_serialize_row(u) for u in upcoming],
        "calendar": [_serialize_row(c) for c in calendar],
        "pending_approvals": [_serialize_row(p) for p in pending],
        "recent_emails": [_serialize_row(e) for e in emails_recent],
        "llm_memory": [_serialize_row(m) for m in memories],
        "links": {
            "dashboard": "http://10.4.7.18:8600",
            "clients": "http://10.4.7.18:8600/clients",
            "deals": "http://10.4.7.18:8600/deals",
            "reports": "http://10.4.7.18:8600/reports",
        },
    }


@app.get("/crm/clients")
def crm_clients(limit: int = Query(default=25, ge=1, le=100)) -> dict[str, Any]:
    rows = _crm_safe_all(
        "SELECT id, name, contact, email, stage, sector, notes FROM clients ORDER BY name LIMIT %s",
        (limit,),
    )
    return {"clients": [_serialize_row(r) for r in rows]}


@app.get("/crm/deals/upcoming")
def crm_deals_upcoming(days: int = Query(default=7, ge=1, le=60)) -> dict[str, Any]:
    rows = _crm_safe_all(
        """
        SELECT d.id, d.title, d.value, d.stage, d.deadline, d.next_action, c.name AS client_name
        FROM deals d LEFT JOIN clients c ON c.id = d.client_id
        WHERE d.deadline IS NOT NULL AND d.deadline <= CURRENT_DATE + make_interval(days => %s)
          AND d.stage NOT IN ('won', 'lost')
        ORDER BY d.deadline ASC LIMIT 30
        """,
        (days,),
    )
    return {"days": days, "deals": [_serialize_row(r) for r in rows]}


@app.get("/emails/recent")
def emails_recent(limit: int = Query(default=20, ge=1, le=100)) -> dict[str, Any]:
    rows = _crm_safe_all(
        """
        SELECT id, message_id, from_addr, to_addrs, subject, direction,
               received_at, sent_at, is_read, client_id
        FROM emails ORDER BY COALESCE(received_at, sent_at) DESC NULLS LAST LIMIT %s
        """,
        (limit,),
    )
    return {"emails": [_serialize_row(r) for r in rows]}


@app.post("/emails/send")
def emails_send(payload: EmailSend) -> dict[str, Any]:
    """Verstuur e-mail via SMTP (configureer SMTP_* env vars). Log in PostgreSQL."""
    import smtplib
    from email.mime.text import MIMEText
    from email.utils import formatdate, make_msgid
    import uuid

    cfg = get_active_email_config()
    smtp_host = cfg["smtp_host"]
    smtp_port = cfg["smtp_port"]
    smtp_user = cfg["smtp_user"]
    smtp_pass = cfg["smtp_pass"]
    smtp_from = cfg["smtp_from"]

    if not smtp_host or not smtp_from:
        raise HTTPException(
            status_code=503,
            detail="Geen email account geconfigureerd. Ga naar Settings → Email in het dashboard.",
        )

    msg = MIMEText(payload.body, "plain", "utf-8")
    msg["Subject"] = payload.subject
    msg["From"] = smtp_from
    msg["To"] = ", ".join(payload.to)
    if payload.cc:
        msg["Cc"] = ", ".join(payload.cc)
    msg["Date"] = formatdate(localtime=True)
    message_id = make_msgid()
    msg["Message-ID"] = message_id

    recipients = list(payload.to) + list(payload.cc)
    try:
        with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as server:
            server.ehlo()
            if smtp_port == 587:
                server.starttls()
            if smtp_user and smtp_pass:
                server.login(smtp_user, smtp_pass)
            server.sendmail(smtp_from, recipients, msg.as_string())
    except Exception as exc:
        raise HTTPException(status_code=502, detail=f"SMTP send failed: {exc}") from exc

    mid = message_id.strip("<>")
    try:
        row = execute_returning(
            """
            INSERT INTO emails (
                message_id, client_id, deal_id, direction, from_addr, to_addrs,
                subject, body_text, sent_at, is_read
            ) VALUES (%s, %s, %s, 'out', %s, %s, %s, %s, NOW(), TRUE)
            RETURNING id, message_id, subject, sent_at
            """,
            (mid, payload.client_id, payload.deal_id, smtp_from, payload.to, payload.subject, payload.body),
        )
    except Exception:
        row = {"message_id": mid, "subject": payload.subject}

    try:
        log_agent_event(
            agent_name="herman",
            event_type="email_sent",
            title=f"Email: {payload.subject[:120]}",
            body=payload.body[:2000],
            metadata={"to": payload.to, "client_id": payload.client_id},
            channel="email",
        )
    except Exception:
        pass

    return {"ok": True, "message_id": mid, "email": _serialize_row(row) if isinstance(row, dict) else row}


@app.post("/llm/memory")
def llm_memory_create(payload: LlmMemoryCreate) -> dict[str, Any]:
    row = execute_returning(
        """
        INSERT INTO llm_memory (category, subject, content, client_id, deal_id, source, metadata)
        VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb)
        RETURNING id, category, subject, content, created_at
        """,
        (
            payload.category,
            payload.subject,
            payload.content,
            payload.client_id,
            payload.deal_id,
            payload.source,
            json.dumps(payload.metadata),
        ),
    )
    return {"ok": True, "memory": _serialize_row(row)}


@app.get("/llm/memory/search")
def llm_memory_search(q: str = Query(..., min_length=1), limit: int = Query(default=15, ge=1, le=50)) -> dict[str, Any]:
    rows = _crm_safe_all(
        """
        SELECT id, category, subject, content, client_id, deal_id, source, updated_at
        FROM llm_memory
        WHERE content ILIKE %s OR subject ILIKE %s OR category ILIKE %s
        ORDER BY updated_at DESC LIMIT %s
        """,
        (f"%{q}%", f"%{q}%", f"%{q}%", limit),
    )
    return {"query": q, "results": [_serialize_row(r) for r in rows]}

@app.get("/settings/email/active")
def settings_email_active() -> dict[str, Any]:
    cfg = get_active_email_config()
    return {
        "configured": bool(cfg.get("smtp_host") and cfg.get("smtp_from")),
        "source": cfg.get("source"),
        "label": cfg.get("label"),
        "from": cfg.get("smtp_from"),
        "account_id": cfg.get("account_id"),
    }

class ImageGenerateBody(BaseModel):
    prompt: str = Field(..., min_length=3, max_length=2000)
    width: int = Field(default=1024, ge=256, le=1024)
    height: int = Field(default=1024, ge=256, le=1024)
    steps: int = Field(default=28, ge=5, le=40)
    seed: Optional[int] = None
    quality: str = Field(default="hd", pattern="^(fast|hd|ultra|custom)$")


class ImageStartBody(BaseModel):
    prompt: str = Field(..., min_length=3, max_length=2000)
    negative_prompt: str = Field(default="blurry, low quality, watermark, text, ugly, deformed", max_length=2000)
    quality: str = Field(default="hd", pattern="^(fast|hd|ultra)$")
    seed: Optional[int] = None


@app.post("/images/generate/start")
async def images_generate_start(body: ImageStartBody) -> dict[str, Any]:
    try:
        return await start_generation(
            body.prompt.strip(),
            negative=body.negative_prompt.strip(),
            quality=body.quality,
            seed=body.seed,
        )
    except Exception as exc:
        raise HTTPException(status_code=502, detail=f"ComfyUI: {exc}") from exc


@app.get("/images/progress/{prompt_id}")
async def images_progress(prompt_id: str) -> dict[str, Any]:
    job = get_job(prompt_id)
    if not job:
        raise HTTPException(status_code=404, detail="Onbekende job")
    return job


@app.get("/images/presets")
def images_presets() -> dict[str, Any]:
    return {"presets": QUALITY_PRESETS}


@app.post("/images/generate")
async def images_generate(body: ImageGenerateBody) -> dict[str, Any]:
    try:
        result = await generate_image(
            body.prompt.strip(),
            width=body.width,
            height=body.height,
            steps=body.steps,
            seed=body.seed,
            quality=body.quality,
        )
    except TimeoutError as exc:
        raise HTTPException(status_code=504, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=502, detail=f"ComfyUI: {exc}") from exc
    try:
        log_agent_event(
            agent_name="design",
            event_type="image_generated",
            title="ComfyUI foto gegenereerd",
            body=body.prompt[:500],
            metadata={"filename": result["filename"], "prompt_id": result["prompt_id"]},
            channel="cockpit",
        )
    except Exception:
        pass
    return {"ok": True, **result}


@app.get("/images/view")
async def images_view(
    filename: str = Query(..., min_length=1),
    subfolder: str = Query(default=""),
    type: str = Query(default="output"),
):
    from fastapi.responses import Response

    try:
        data = await fetch_image_bytes(filename, subfolder, type)
    except Exception as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    media = "image/png" if filename.lower().endswith(".png") else "image/jpeg"
    return Response(content=data, media_type=media)

@app.post("/brain/messages")
async def brain_store_message(body: BrainMessageIn) -> dict[str, Any]:
    try:
        if body.embed and (body.content_text or "").strip():
            row = await brain_svc.store_message_with_embedding(
                body.chat_id,
                direction=body.direction,
                content_text=body.content_text,
                content_type=body.content_type,
                role=body.role,
                telegram_message_id=body.telegram_message_id,
                reply_to_db_id=body.reply_to_db_id,
                agent_name=body.agent_name,
                content_json=body.content_json,
                user_name=body.user_name,
                user_role=body.user_role,
                chat_type=body.chat_type,
            )
        else:
            row = brain_svc.store_message(
                body.chat_id,
                direction=body.direction,
                content_text=body.content_text,
                content_type=body.content_type,
                role=body.role,
                telegram_message_id=body.telegram_message_id,
                reply_to_db_id=body.reply_to_db_id,
                agent_name=body.agent_name,
                content_json=body.content_json,
                user_name=body.user_name,
                user_role=body.user_role,
                chat_type=body.chat_type,
            )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return _serialize_row(row)


@app.post("/brain/edges")
def brain_store_edge(body: BrainEdgeIn) -> dict[str, Any]:
    try:
        row = brain_svc.store_edge(
            body.source_message_id,
            body.edge_type,
            target_message_id=body.target_message_id,
            target_entity_type=body.target_entity_type,
            target_entity_id=body.target_entity_id,
            weight=body.weight,
            metadata=body.metadata,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return _serialize_row(row)


@app.get("/brain/graph/{chat_id}")
def brain_graph(chat_id: int, limit: int = Query(default=50, ge=1, le=200)) -> dict[str, Any]:
    return brain_svc.get_graph(chat_id, limit=limit)


@app.post("/brain/search")
async def brain_search(body: BrainSearchIn) -> dict[str, Any]:
    try:
        results = await brain_svc.search_memory(body.query, chat_id=body.chat_id, limit=body.limit)
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return {"results": [_serialize_row(r) for r in results]}

# Append to tools-api main.py before end

@app.get("/brain/conversations")
def brain_list_conversations(limit: int = Query(default=50, ge=1, le=200)) -> dict[str, Any]:
    rows = brain_svc.list_conversations(limit=limit)
    return {"items": [_serialize_row(r) for r in rows]}


@app.get("/brain/feed")
def brain_feed(
    chat_id: Optional[int] = Query(default=None),
    limit: int = Query(default=80, ge=1, le=200),
    offset: int = Query(default=0, ge=0),
) -> dict[str, Any]:
    rows = brain_svc.list_feed(chat_id=chat_id, limit=limit, offset=offset)
    return {"items": [_serialize_row(r) for r in rows]}


@app.get("/brain/stats")
def brain_stats() -> dict[str, Any]:
    data = brain_svc.get_dashboard_stats()
    return {
        "stats": _serialize_row(data.get("stats") or {}),
        "recent_messages": [_serialize_row(r) for r in data.get("recent_messages") or []],
        "agent_events": [_serialize_row(r) for r in data.get("agent_events") or []],
    }


@app.get("/brain/graph")
def brain_global_graph(limit: int = Query(default=100, ge=1, le=300)) -> dict[str, Any]:
    return brain_svc.get_global_graph(limit=limit)
