"""Agentic API — Spec 029."""
from __future__ import annotations

import json
from datetime import datetime, timezone

from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field

from app import agent_bindings, auth
from app.agents import llm_client, runner, store
from app.agents import messages as agent_messages
from app.agents.catalog import AGENT_CATALOG, roster_public
from app.agents.chat_stream import chat_stream_generator, _thread_history_messages
from app.agents.chat_context_builder import build_ops_context

router = APIRouter(prefix="/api/v1/agents", tags=["agents"])


def _db():
    conn = auth.db()
    try:
        yield conn
    finally:
        conn.close()


def _ops_view(user, conn):
    if not agent_bindings.can_use_agentics_ui(conn, user.role):
        raise HTTPException(403, "insufficient permissions")


def _require_agent_chat(user, conn, agent_id: str):
    _ops_view(user, conn)
    if not agent_bindings.can_chat_agent(conn, user.role, agent_id):
        raise HTTPException(403, f"sem atribuição Agentics para agente {agent_id}")


class ChatRequest(BaseModel):
    question: str = Field(..., min_length=2, max_length=4000)
    include_findings: bool = True
    target_agent: str = Field(default="A6", description="Agente destino — default Copiloto")


class ReplyRequest(BaseModel):
    body: str = Field(..., min_length=1, max_length=8000)
    target_agent: str | None = None


@router.get("/health")
def agents_health():
    return {"status": "ok", **llm_client.llm_status()}


@router.get("/overview")
def agents_overview(user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    return store.get_overview(conn)


@router.get("/incidents")
def agents_incidents(
    user=Depends(auth.get_current_user),
    conn=Depends(_db),
    status: str = Query("open"),
    severity: str | None = None,
    agent_id: str | None = None,
    limit: int = Query(50, ge=1, le=200),
):
    _ops_view(user, conn)
    return {"incidents": store.list_incidents(conn, status=status, severity=severity, agent_id=agent_id, limit=limit)}


@router.get("/incidents/{incident_id}")
def agents_incident_detail(incident_id: int, user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    inc = store.get_incident(conn, incident_id)
    if not inc:
        raise HTTPException(404, "incident not found")
    return {
        "incident": inc,
        "recent_runs": store.recent_runs_for_scenario(conn, inc["scenario_id"]),
        "thread_id": inc.get("thread_id"),
    }


@router.post("/incidents/{incident_id}/ack")
def ack_incident(incident_id: int, user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    inc = store.ack_incident(conn, incident_id, user.username)
    if not inc:
        raise HTTPException(404, "incident not found")
    store.log_event(conn, event_type="incident.ack", message=f"#{incident_id}", payload={"by": user.username})
    conn.commit()
    return {"ok": True, "incident_id": incident_id, "thread_id": inc.get("thread_id")}


@router.get("/timeline")
def agents_timeline(user=Depends(auth.get_current_user), conn=Depends(_db), limit: int = Query(24, ge=1, le=100)):
    _ops_view(user, conn)
    ticks = [
        dict(r)
        for r in conn.execute(
            """SELECT ts, message, payload_json FROM agent_action_log
               WHERE event_type='tick.complete' ORDER BY id DESC LIMIT ?""",
            (limit,),
        )
    ]
    out = []
    for t in ticks:
        payload = {}
        try:
            payload = json.loads(t.get("payload_json") or "{}")
        except json.JSONDecodeError:
            pass
        runs = payload.get("runs") or []
        findings = sum(r.get("findings_count", 0) for r in runs if isinstance(r, dict))
        out.append({"at": t["ts"], "scenarios": payload.get("total", len(runs)), "findings": findings})
    return {"ticks": out}


@router.get("/roster")
def agents_roster(user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    return {"agents": roster_public()}


@router.get("/inbox")
def agents_inbox(user=Depends(auth.get_current_user), conn=Depends(_db), limit: int = Query(50, ge=1, le=200)):
    _ops_view(user, conn)
    return {"messages": agent_messages.list_inbox(conn, role=user.role, limit=limit)}


@router.get("/threads")
def agents_threads(user=Depends(auth.get_current_user), conn=Depends(_db), limit: int = Query(40, ge=1, le=100)):
    _ops_view(user, conn)
    return {"threads": agent_messages.list_threads(conn, limit=limit)}


@router.get("/threads/{thread_id}/messages")
def thread_messages(thread_id: int, user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    if not conn.execute("SELECT id FROM agent_threads WHERE id=?", (thread_id,)).fetchone():
        raise HTTPException(404, "thread not found")
    return {"thread_id": thread_id, "messages": agent_messages.thread_messages(conn, thread_id)}


def _agent_profile(agent_id: str):
    return AGENT_CATALOG.get(agent_id, AGENT_CATALOG["A6"])


def _findings_summary(conn, include: bool) -> str:
    if not include:
        return ""
    open_f = store.list_findings(conn, limit=8, open_only=True)
    if not open_f:
        return ""
    return "\n".join(
        f"- [{f['severity']}] {f['title']}: {f.get('suggested_human_action') or ''}" for f in open_f
    )


def _thread_history(conn, thread_id: int, limit: int = 10) -> str:
    msgs = agent_messages.thread_messages(conn, thread_id)[-limit:]
    return "\n".join(f"{m.get('from_label') or m.get('from_id')}: {m.get('body', '')[:240]}" for m in msgs)


def _chat_ops_context(conn, question: str, target_agent: str, include: bool) -> tuple[list[str], str]:
    kb = store.search_kb(conn, question)
    snippets = [k["snippet"] for k in kb]
    ctx = build_ops_context(
        conn, question, target_agent, include_findings=include, kb_snippets=snippets
    )
    return snippets, ctx


@router.post("/threads/{thread_id}/reply")
def thread_reply(
    thread_id: int,
    body: ReplyRequest,
    user=Depends(auth.get_current_user),
    conn=Depends(_db),
):
    _ops_view(user, conn)
    row = conn.execute("SELECT * FROM agent_threads WHERE id=?", (thread_id,)).fetchone()
    if not row:
        raise HTTPException(404, "thread not found")
    target = body.target_agent or row["primary_agent"]
    mid = agent_messages.human_reply(
        conn, thread_id=thread_id, username=user.username, body=body.body, target_agent=target
    )
    store.log_event(
        conn,
        event_type="human.reply",
        message=body.body[:120],
        agent_id=target,
        payload={"thread_id": thread_id, "user": user.username},
    )
    conn.commit()
    return {"ok": True, "message_id": mid}


@router.post("/threads/{thread_id}/chat")
def thread_chat(
    thread_id: int,
    body: ChatRequest,
    user=Depends(auth.get_current_user),
    conn=Depends(_db),
):
    """Continuar conversa com agente (LLM) numa thread existente."""
    row = conn.execute("SELECT * FROM agent_threads WHERE id=?", (thread_id,)).fetchone()
    if not row:
        raise HTTPException(404, "thread not found")
    target = body.target_agent or row["primary_agent"]
    _require_agent_chat(user, conn, target)
    profile = _agent_profile(target)
    kb_snippets, ops_context = _chat_ops_context(conn, body.question, target, body.include_findings)
    answer, model = llm_client.chat_context(
        question=body.question,
        kb_snippets=kb_snippets,
        ops_context=ops_context,
        user_role=user.role,
        target_agent=target,
        agent_name=profile.name,
        agent_role=profile.role,
        history_messages=_thread_history_messages(conn, thread_id),
    )
    agent_messages.post_message(
        conn,
        thread_id=thread_id,
        from_type="human",
        from_id=user.username,
        to_type="agent",
        to_id=target,
        body=body.question,
    )
    agent_messages.post_message(
        conn,
        thread_id=thread_id,
        from_type="agent",
        from_id=target,
        to_type="human",
        to_id=user.username,
        body=answer,
        context={"model": model, "kb_hits": len(kb_snippets)},
    )
    store.log_event(
        conn,
        event_type="chat.thread",
        message=body.question[:120],
        agent_id=target,
        payload={"user": user.username, "model": model, "thread_id": thread_id},
    )
    conn.commit()
    return {"answer": answer, "model": model, "kb_hits": len(kb_snippets), "thread_id": thread_id}


@router.post("/threads/{thread_id}/chat/stream")
def thread_chat_stream(
    thread_id: int,
    body: ChatRequest,
    user=Depends(auth.get_current_user),
):
    """Chat com streaming SSE — resposta token a token."""
    _ops_view(user, conn)
    conn = auth.db()
    try:
        row = conn.execute("SELECT * FROM agent_threads WHERE id=?", (thread_id,)).fetchone()
        if not row:
            raise HTTPException(404, "thread not found")
        target = body.target_agent or row["primary_agent"]
        profile = _agent_profile(target)
    finally:
        conn.close()
    return StreamingResponse(
        chat_stream_generator(
            username=user.username,
            user_role=user.role,
            question=body.question,
            target_agent=target,
            agent_name=profile.name,
            agent_role=profile.role,
            include_findings=body.include_findings,
            thread_id=thread_id,
        ),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@router.post("/messages/{message_id}/ack")
def ack_inbox_message(message_id: int, user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    if not agent_messages.ack_message(conn, message_id, user.username):
        raise HTTPException(404, "not found")
    conn.commit()
    return {"ok": True, "id": message_id}


@router.get("/scenarios")
def list_scenarios(user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    runner.sync_registry(conn)
    conn.commit()
    return {"scenarios": store.list_scenarios(conn)}


@router.get("/findings")
def list_findings(
    user=Depends(auth.get_current_user),
    conn=Depends(_db),
    severity: str | None = None,
    limit: int = Query(50, ge=1, le=200),
    open_only: bool = True,
):
    _ops_view(user, conn)
    return {"findings": store.list_findings(conn, severity=severity, limit=limit, open_only=open_only)}


@router.post("/findings/{finding_id}/ack")
def ack_finding(finding_id: int, user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    if not conn.execute("SELECT id FROM agent_findings WHERE id=?", (finding_id,)).fetchone():
        raise HTTPException(404, "not found")
    now = datetime.now(timezone.utc).isoformat()
    conn.execute(
        "UPDATE agent_findings SET acknowledged_at=?, acknowledged_by=? WHERE id=?",
        (now, user.username, finding_id),
    )
    store.log_event(conn, event_type="finding.ack", message=f"#{finding_id}", payload={"by": user.username})
    conn.commit()
    return {"ok": True, "id": finding_id}


@router.get("/action-log")
def action_log(user=Depends(auth.get_current_user), conn=Depends(_db), limit: int = Query(100, ge=1, le=500)):
    _ops_view(user, conn)
    return {"events": store.list_action_log(conn, limit=limit)}


@router.get("/kb/sources")
def kb_sources(user=Depends(auth.get_current_user), conn=Depends(_db)):
    _ops_view(user, conn)
    rows = conn.execute(
        """SELECT source_path, COUNT(*) AS chunks, MAX(indexed_at) AS indexed_at
           FROM agent_kb_chunks GROUP BY source_path ORDER BY source_path"""
    ).fetchall()
    total = conn.execute("SELECT COUNT(*) c FROM agent_kb_chunks").fetchone()["c"]
    return {"sources": [dict(r) for r in rows], "total_chunks": total}


@router.get("/kb/search")
def kb_search(
    user=Depends(auth.get_current_user),
    conn=Depends(_db),
    q: str = Query(..., min_length=1, max_length=500),
    limit: int = Query(12, ge=1, le=50),
):
    _ops_view(user, conn)
    hits = store.search_kb(conn, q, limit=limit)
    return {"query": q, "hits": hits, "count": len(hits)}


@router.post("/runs/{scenario_id}")
def trigger_run(scenario_id: str, user=Depends(auth.get_current_user), conn=Depends(_db)):
    if not agent_bindings.can_trigger_runs(conn, user.role):
        raise HTTPException(403, "insufficient permissions")
    r = runner.run_scenario(conn, scenario_id, trigger=f"manual:{user.username}")
    conn.commit()
    return r


@router.post("/chat")
def agent_chat(body: ChatRequest, user=Depends(auth.get_current_user), conn=Depends(_db)):
    """Janela de contexto T1 — humano ↔ agente (default Copiloto A6)."""
    _require_agent_chat(user, conn, body.target_agent)
    profile = _agent_profile(body.target_agent)
    kb_snippets, ops_context = _chat_ops_context(conn, body.question, body.target_agent, body.include_findings)
    answer, model = llm_client.chat_context(
        question=body.question,
        kb_snippets=kb_snippets,
        ops_context=ops_context,
        user_role=user.role,
        target_agent=body.target_agent,
        agent_name=profile.name,
        agent_role=profile.role,
    )
    thread_id = agent_messages.create_thread(
        conn,
        subject=f"Chat: {body.question[:60]}",
        primary_agent=body.target_agent,
        severity="info",
    )
    agent_messages.post_message(
        conn,
        thread_id=thread_id,
        from_type="human",
        from_id=user.username,
        to_type="agent",
        to_id=body.target_agent,
        body=body.question,
    )
    agent_messages.post_message(
        conn,
        thread_id=thread_id,
        from_type="agent",
        from_id=body.target_agent,
        to_type="human",
        to_id=user.username,
        body=answer,
        context={"model": model, "kb_hits": len(kb_snippets)},
    )
    store.log_event(
        conn,
        event_type="chat.query",
        message=body.question[:120],
        agent_id=body.target_agent,
        payload={"user": user.username, "model": model, "thread_id": thread_id},
    )
    conn.commit()
    return {"answer": answer, "model": model, "kb_hits": len(kb_snippets), "thread_id": thread_id}


@router.post("/chat/stream")
def agent_chat_stream(body: ChatRequest, user=Depends(auth.get_current_user), conn=Depends(_db)):
    """Nova conversa com streaming SSE."""
    _require_agent_chat(user, conn, body.target_agent)
    profile = _agent_profile(body.target_agent)
    return StreamingResponse(
        chat_stream_generator(
            username=user.username,
            user_role=user.role,
            question=body.question,
            target_agent=body.target_agent,
            agent_name=profile.name,
            agent_role=profile.role,
            include_findings=body.include_findings,
            thread_id=None,
        ),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@router.post("/internal/tick")
def internal_tick(user=Depends(auth.require_internal_or_user), conn=Depends(_db)):
    kb = runner.index_specs_kb(conn)
    result = runner.run_all_enabled(conn, trigger="cron")
    store.log_event(
        conn,
        event_type="tick.complete",
        message=f"kb={kb} runs={result['total']}",
        agent_id="A0",
        payload={"kb": kb, **result},
    )
    conn.commit()
    return {"ok": True, "kb_indexed": kb, **result}
