"""Tools de navegacao — listar, visao geral, ls, read."""

import asyncio
import logging
import time
from typing import Any, Literal

from fastmcp import FastMCP

from services.supabase_client import get_supabase_client
from services.usage_logger import get_usage_logger, ms_since
from tools._helpers import (
    MSG_DOCUMENTO_NAO_ENCONTRADO,
    MSG_PROCESSO_NAO_ENCONTRADO,
    SYNC_URL,
    _get_current_user,
    erro_instrutivo,
    paginar_resultado,
)

logger = logging.getLogger("tecjustica-mcp.navegacao")


def register_navegacao_tools(mcp: FastMCP) -> None:
    """Registra tools de navegacao."""

    @mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": False})
    async def listar_processos(
        busca: str | None = None,
        limite: int = 20,
        offset: int = 0,
    ) -> dict[str, Any]:
        """
        Lista processos disponiveis ou busca por nome de parte.

        QUANDO USAR:
        - Descobrir quais processos existem no sistema
        - Buscar processo pelo nome de uma parte (autor, reu, advogado)
        - Primeira tool a chamar quando o usuario menciona um processo sem dar o numero

        QUANDO NAO USAR:
        - Ja tem o numero do processo → use visao_geral_processo()
        - Quer documentos ou movimentacoes → use ls_documentos() ou ls_movimentacoes()

        Args:
            busca: Nome da parte para buscar (opcional). Se omitido, lista todos.
            limite: Maximo de resultados (default 20, max 50).
            offset: Pular N resultados para paginacao.

        Returns:
            Lista de processos com numero, classe, tribunal, partes resumidas e paginacao.
        """
        _start = time.perf_counter()
        user_id, user_email = _get_current_user()
        limite = min(limite, 50)

        try:
            sb = get_supabase_client()

            if busca:
                # Busca por nome de parte via RPC existente
                dados = await sb.buscar_por_parte(busca)
                total = len(dados)
                pagina = dados[offset : offset + limite]
                processos = [
                    {
                        "numero_processo": p.get("numero_processo"),
                        "classe_descricao": p.get("classe_descricao"),
                        "sigla_tribunal": p.get("sigla_tribunal"),
                        "data_ajuizamento": p.get("data_ajuizamento"),
                        "valor_causa": p.get("valor_causa"),
                    }
                    for p in pagina
                ]
            else:
                # Lista todos via jobs (completed) — pede mais para compensar duplicatas
                response = (
                    sb.client.table("jobs")
                    .select(
                        "numero_processo, processos(numero_processo, classe_descricao, sigla_tribunal, data_ajuizamento, valor_causa, partes)",
                        count="exact",
                    )
                    .eq("status", "completed")
                    .order("created_at", desc=True)
                    .range(0, offset + limite * 2 - 1)
                    .execute()
                )
                # Deduplicar por numero_processo
                seen: set[str] = set()
                todos_processos = []
                for job in response.data or []:
                    np = job.get("numero_processo")
                    if not np or np in seen:
                        continue
                    seen.add(np)
                    proc_data = job.get("processos")
                    # PostgREST join pode retornar lista ou dict
                    if isinstance(proc_data, list):
                        proc = proc_data[0] if proc_data else {}
                    else:
                        proc = proc_data or {}
                    partes = proc.get("partes") or []
                    partes_resumo = ", ".join(p.get("nome", "") for p in partes[:3])
                    if len(partes) > 3:
                        partes_resumo += f" (+{len(partes) - 3})"
                    todos_processos.append(
                        {
                            "numero_processo": np,
                            "classe_descricao": proc.get("classe_descricao"),
                            "sigla_tribunal": proc.get("sigla_tribunal"),
                            "data_ajuizamento": proc.get("data_ajuizamento"),
                            "valor_causa": proc.get("valor_causa"),
                            "partes": partes_resumo,
                        }
                    )
                total = len(todos_processos)
                processos = todos_processos[offset : offset + limite]

            resultado = paginar_resultado(processos, total, limite, offset)
            resultado["dica"] = (
                "Use visao_geral_processo(numero_processo) para ver detalhes de um processo."
            )

            try:
                get_usage_logger().log_tool_call(
                    tool_name="listar_processos",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    params={"busca": busca, "limite": limite, "offset": offset},
                )
            except Exception:
                pass
            return resultado
        except Exception as e:
            try:
                get_usage_logger().log_tool_call(
                    tool_name="listar_processos",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    success=False,
                    error_message=str(e),
                )
            except Exception:
                pass
            return erro_instrutivo(
                str(e),
                f"Verifique a conexao ou sincronize processos em {SYNC_URL}",
            )

    @mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": False})
    async def visao_geral_processo(
        numero_processo: str,
    ) -> dict[str, Any]:
        """
        Visao geral consolidada de um processo: metadados, partes, stats e movimentacoes recentes.

        QUANDO USAR:
        - Ponto de entrada ao investigar um processo especifico
        - Entender a estrutura antes de mergulhar nos documentos
        - Obter resumo rapido: classe, partes, valor, stats de docs, ultimas movimentacoes

        QUANDO NAO USAR:
        - Nao sabe o numero do processo → use listar_processos()
        - Quer lista completa de documentos → use ls_documentos()
        - Quer buscar conteudo → use grep_documentos()

        Args:
            numero_processo: Numero CNJ do processo (formato NNNNNNN-DD.AAAA.J.TR.OOOO).

        Returns:
            Metadados do processo, partes, assuntos, estatisticas de documentos e ultimas movimentacoes.
        """
        _start = time.perf_counter()
        user_id, user_email = _get_current_user()

        try:
            sb = get_supabase_client()

            # 3 queries em paralelo via asyncio.to_thread
            # Usa .limit(1) em vez de .maybe_single() para evitar bug do
            # postgrest-py que levanta "Missing response" code 204 quando
            # PostgREST retorna erro sem "The result contains 0 rows".
            proc_task = asyncio.to_thread(
                lambda: (
                    sb.client.table("vw_processo_completo")
                    .select("*")
                    .eq("numero_processo", numero_processo)
                    .limit(1)
                    .execute()
                )
            )
            stats_task = asyncio.to_thread(
                lambda: sb.client.rpc(
                    "stats_documentos",
                    {"p_numero_processo": numero_processo},
                ).execute()
            )
            movs_task = asyncio.to_thread(
                lambda: (
                    sb.client.table("movimentacoes")
                    .select(
                        "id, sequencia, data_hora, tipo_nome, descricao, magistrado_nome, source"
                    )
                    .eq("numero_processo", numero_processo)
                    .order("data_hora", desc=True)
                    .limit(10)
                    .execute()
                )
            )
            docs_task = asyncio.to_thread(
                lambda: (
                    sb.client.table("documents")
                    .select(
                        "id, id_origem, sequencia, nome, tipo_nome, data_juntada, tamanho_texto, paginas, source, confianca"
                    )
                    .eq("numero_processo", numero_processo)
                    .order("sequencia", desc=True)
                    .limit(200)
                    .execute()
                )
            )

            # return_exceptions=True para que falha em uma query nao cancele as outras
            proc_resp, stats_resp, movs_resp, docs_resp = await asyncio.gather(
                proc_task, stats_task, movs_task, docs_task, return_exceptions=True
            )

            # Processar resposta do processo (agora e lista, nao single)
            processo = None
            if not isinstance(proc_resp, Exception):
                proc_data = proc_resp.data or []
                processo = proc_data[0] if proc_data else None
            # Fallback para tabela principal se view falhar
            if not processo:
                try:
                    fallback = await asyncio.to_thread(
                        lambda: (
                            sb.client.table("processos")
                            .select("*")
                            .eq("numero_processo", numero_processo)
                            .limit(1)
                            .execute()
                        )
                    )
                    fallback_data = fallback.data or []
                    processo = fallback_data[0] if fallback_data else None
                except Exception:
                    pass

            if not processo:
                try:
                    get_usage_logger().log_tool_call(
                        tool_name="visao_geral_processo",
                        duration_ms=ms_since(_start),
                        user_id=user_id,
                        user_email=user_email,
                        success=False,
                        error_message="processo_nao_encontrado",
                        params={"numero_processo": numero_processo},
                    )
                except Exception:
                    pass
                return erro_instrutivo(
                    MSG_PROCESSO_NAO_ENCONTRADO,
                    "Use listar_processos() para ver processos disponiveis.",
                )

            # Processar stats (pode ser Exception se a query falhou)
            stats_data = None
            if not isinstance(stats_resp, Exception):
                stats_data = stats_resp.data
                if isinstance(stats_data, list) and stats_data:
                    stats_data = stats_data[0]

            # Processar movimentacoes (pode ser Exception se a query falhou)
            movs_data = [] if isinstance(movs_resp, Exception) else (movs_resp.data or [])
            ultimas_movs = []
            for mov in movs_data:
                desc = mov.get("descricao") or ""
                if len(desc) > 300:
                    desc = desc[:300] + "..."
                ultimas_movs.append(
                    {
                        "id": mov.get("id"),
                        "sequencia": mov.get("sequencia"),
                        "data_hora": mov.get("data_hora"),
                        "tipo_nome": mov.get("tipo_nome"),
                        "descricao": desc,
                        "magistrado_nome": mov.get("magistrado_nome"),
                        "fonte": mov.get("source"),
                    }
                )

            # Processar catalogo de documentos (pode ser Exception se a query falhou)
            docs_catalogo = []
            if not isinstance(docs_resp, Exception):
                for doc in docs_resp.data or []:
                    docs_catalogo.append(
                        {
                            "id": doc.get("id"),
                            "id_origem": doc.get("id_origem"),
                            "sequencia": doc.get("sequencia"),
                            "nome": doc.get("nome"),
                            "tipo_nome": doc.get("tipo_nome"),
                            "data_juntada": doc.get("data_juntada"),
                            "tamanho_texto": doc.get("tamanho_texto"),
                            "paginas": doc.get("paginas"),
                            "fonte": doc.get("source"),
                            "confianca": doc.get("confianca"),
                        }
                    )

            resultado: dict[str, Any] = {
                "numero_processo": numero_processo,
                "classe_descricao": processo.get("classe_descricao"),
                "sigla_tribunal": processo.get("sigla_tribunal"),
                "instancia": processo.get("instancia"),
                "natureza": processo.get("natureza"),
                "valor_causa": processo.get("valor_causa"),
                "data_ajuizamento": processo.get("data_ajuizamento"),
                "justica_gratuita": processo.get("justica_gratuita"),
                "partes": processo.get("partes", []),
                "assuntos": processo.get("assuntos", []),
                "stats_documentos": stats_data,
                "documentos": docs_catalogo,
                "ultimas_movimentacoes": ultimas_movs,
                "dica": (
                    "Catalogo de documentos incluido acima com IDs. "
                    "Use analisar(numero_processo, pergunta, document_ids='id1,id2') para analisar docs especificos, "
                    "ou analisar(numero_processo, pergunta) para analisar o processo inteiro. "
                    "Use read_documento(id) para ler conteudo completo de um documento."
                ),
            }

            try:
                get_usage_logger().log_tool_call(
                    tool_name="visao_geral_processo",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    params={"numero_processo": numero_processo},
                )
            except Exception:
                pass
            return resultado
        except Exception as e:
            try:
                get_usage_logger().log_tool_call(
                    tool_name="visao_geral_processo",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    success=False,
                    error_message=str(e),
                )
            except Exception:
                pass
            return erro_instrutivo(str(e), "Verifique o numero do processo.")

    @mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": False})
    async def ls_documentos(
        numero_processo: str,
        limite: int = 30,
        offset: int = 0,
        ordem: Literal["recentes", "antigos"] = "recentes",
        fonte: str | None = None,
    ) -> dict[str, Any]:
        """
        Lista documentos de um processo SEM conteudo (apenas metadados).

        QUANDO USAR:
        - Ver catalogo de documentos com tipos, datas, tamanhos
        - Obter IDs para usar com read_documento() ou localizar_no_documento()
        - Navegar documentos com paginacao

        QUANDO NAO USAR:
        - Quer ler o conteudo de um documento → use read_documento()
        - Quer buscar termo no conteudo → use grep_documentos()
        - Quer filtrar por nome/tipo → use glob_documentos()

        Args:
            numero_processo: Numero CNJ do processo.
            limite: Maximo de documentos por pagina (default 30, max 100).
            offset: Pular N documentos para paginacao.
            ordem: "recentes" (mais novos primeiro) ou "antigos" (mais velhos primeiro).
            fonte: Filtrar por origem dos dados: "api" ou "segmentation" (opcional, default=ambas).

        Returns:
            Lista de documentos com metadados (incluindo fonte e confianca) e paginacao. Nenhum conteudo e retornado.
        """
        _start = time.perf_counter()
        user_id, user_email = _get_current_user()
        limite = min(limite, 100)

        try:
            sb = get_supabase_client().client

            query = (
                sb.table("documents")
                .select(
                    "id, id_origem, numero_processo, tipo_nome, nome, sequencia, data_juntada, "
                    "tamanho_texto, tamanho_bytes, paginas, nivel_sigilo, metodo_extracao, "
                    "signatarios, tipo_arquivo, source, confianca",
                    count="exact",
                )
                .eq("numero_processo", numero_processo)
            )

            if fonte:
                query = query.eq("source", fonte)

            query = query.order("sequencia", desc=(ordem == "recentes")).range(
                offset, offset + limite - 1
            )

            response = query.execute()
            total = response.count or 0
            documentos = response.data or []

            resultado = paginar_resultado(documentos, total, limite, offset)
            resultado["dica"] = (
                "Use os IDs acima com analisar(numero_processo, pergunta, document_ids='id1,id2') "
                "para analise com LLM de documentos especificos. "
                "Use read_documento(id) para ler conteudo, "
                "grep_documentos(padrao) para buscar texto."
            )

            try:
                get_usage_logger().log_tool_call(
                    tool_name="ls_documentos",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    params={
                        "numero_processo": numero_processo,
                        "limite": limite,
                        "offset": offset,
                        "ordem": ordem,
                        "fonte": fonte,
                    },
                )
            except Exception:
                pass
            return resultado
        except Exception as e:
            try:
                get_usage_logger().log_tool_call(
                    tool_name="ls_documentos",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    success=False,
                    error_message=str(e),
                )
            except Exception:
                pass
            return erro_instrutivo(str(e), MSG_PROCESSO_NAO_ENCONTRADO)

    @mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": False})
    async def ls_movimentacoes(
        numero_processo: str,
        limite: int = 30,
        offset: int = 0,
        ordem: Literal["recentes", "antigos"] = "recentes",
        fonte: str | None = None,
    ) -> dict[str, Any]:
        """
        Lista movimentacoes (timeline) de um processo.

        QUANDO USAR:
        - Ver historico cronologico do processo
        - Verificar ultimas movimentacoes e andamentos
        - Identificar datas de audiencias, decisoes, publicacoes

        QUANDO NAO USAR:
        - Quer buscar termo nas movimentacoes → use grep_movimentacoes()
        - Quer visao geral do processo → use visao_geral_processo()

        Args:
            numero_processo: Numero CNJ do processo.
            limite: Maximo de movimentacoes por pagina (default 30, max 100).
            offset: Pular N movimentacoes para paginacao.
            ordem: "recentes" (mais novas primeiro) ou "antigos" (mais velhas primeiro).
            fonte: Filtrar por origem dos dados: "api" ou "segmentation" (opcional, default=ambas).

        Returns:
            Lista de movimentacoes com data, tipo, descricao, magistrado, fonte e paginacao.
        """
        _start = time.perf_counter()
        user_id, user_email = _get_current_user()
        limite = min(limite, 100)

        try:
            sb = get_supabase_client().client

            query = (
                sb.table("movimentacoes")
                .select(
                    "id, sequencia, data_hora, tipo_nome, descricao, "
                    "magistrado_nome, orgao_julgador_nome, documento_id, source",
                    count="exact",
                )
                .eq("numero_processo", numero_processo)
            )

            if fonte:
                query = query.eq("source", fonte)

            query = query.order("data_hora", desc=(ordem == "recentes")).range(
                offset, offset + limite - 1
            )

            response = query.execute()
            total = response.count or 0

            movimentacoes = []
            for mov in response.data or []:
                desc = mov.get("descricao") or ""
                if len(desc) > 300:
                    desc = desc[:300] + "..."
                movimentacoes.append(
                    {
                        "id": mov.get("id"),
                        "sequencia": mov.get("sequencia"),
                        "data_hora": mov.get("data_hora"),
                        "tipo_nome": mov.get("tipo_nome"),
                        "descricao": desc,
                        "magistrado_nome": mov.get("magistrado_nome"),
                        "orgao_julgador_nome": mov.get("orgao_julgador_nome"),
                        "documento_id": mov.get("documento_id"),
                        "fonte": mov.get("source"),
                    }
                )

            resultado = paginar_resultado(movimentacoes, total, limite, offset)
            resultado["dica"] = (
                "Use grep_movimentacoes(padrao) para buscar termos especificos nas movimentacoes."
            )

            try:
                get_usage_logger().log_tool_call(
                    tool_name="ls_movimentacoes",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    params={
                        "numero_processo": numero_processo,
                        "limite": limite,
                        "offset": offset,
                        "ordem": ordem,
                        "fonte": fonte,
                    },
                )
            except Exception:
                pass
            return resultado
        except Exception as e:
            try:
                get_usage_logger().log_tool_call(
                    tool_name="ls_movimentacoes",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    success=False,
                    error_message=str(e),
                )
            except Exception:
                pass
            return erro_instrutivo(str(e), MSG_PROCESSO_NAO_ENCONTRADO)

    @mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": False})
    async def read_documento(
        document_id: str,
        max_chars: int = 10000,
        offset: int = 0,
    ) -> dict[str, Any]:
        """
        Le o conteudo de um documento com controle de tamanho via max_chars e offset.

        QUANDO USAR:
        - Ler conteudo textual de um documento especifico
        - Leitura parcial controlada: primeiro trecho, depois proximo offset
        - Verificar conteudo apos encontrar via grep_documentos() ou localizar_no_documento()

        QUANDO NAO USAR:
        - Nao sabe o ID do documento → use ls_documentos() ou grep_documentos()
        - Quer buscar termo em varios documentos → use grep_documentos()
        - Quer apenas metadados → use ls_documentos()

        Args:
            document_id: UUID do documento (obtido via ls_documentos, grep_documentos, etc.).
            max_chars: Maximo de caracteres a retornar (default 10000). Ajuste conforme necessidade.
            offset: Posicao inicial no texto (default 0). Use para continuar leitura.

        Returns:
            Metadados do documento + trecho do conteudo + informacoes de leitura (offset, tem_mais, proximo_offset).
        """
        _start = time.perf_counter()
        user_id, user_email = _get_current_user()

        try:
            sb = get_supabase_client().client

            # Usa .limit(1) em vez de .maybe_single() para evitar bug do
            # postgrest-py "Missing response" code 204.
            response = (
                sb.table("documents")
                .select(
                    "id, id_origem, numero_processo, tipo_nome, nome, sequencia, "
                    "data_juntada, nivel_sigilo, tamanho_texto, paginas, "
                    "metodo_extracao, signatarios, conteudo, source, confianca"
                )
                .eq("id", document_id)
                .limit(1)
                .execute()
            )

            docs = response.data or []
            doc = docs[0] if docs else None
            if not doc:
                try:
                    get_usage_logger().log_tool_call(
                        tool_name="read_documento",
                        duration_ms=ms_since(_start),
                        user_id=user_id,
                        user_email=user_email,
                        success=False,
                        error_message="documento_nao_encontrado",
                        params={"document_id": document_id},
                    )
                except Exception:
                    pass
                return erro_instrutivo(
                    MSG_DOCUMENTO_NAO_ENCONTRADO,
                    "Use ls_documentos(numero_processo) para obter IDs validos.",
                )

            conteudo_completo = doc.get("conteudo") or ""
            tamanho_total = len(conteudo_completo)

            # Slice controlado
            trecho = conteudo_completo[offset : offset + max_chars]
            chars_retornados = len(trecho)
            tem_mais = offset + chars_retornados < tamanho_total

            resultado: dict[str, Any] = {
                "id": doc.get("id"),
                "id_origem": doc.get("id_origem"),
                "numero_processo": doc.get("numero_processo"),
                "tipo_nome": doc.get("tipo_nome"),
                "nome": doc.get("nome"),
                "sequencia": doc.get("sequencia"),
                "data_juntada": doc.get("data_juntada"),
                "nivel_sigilo": doc.get("nivel_sigilo"),
                "tamanho_texto": doc.get("tamanho_texto"),
                "paginas": doc.get("paginas"),
                "metodo_extracao": doc.get("metodo_extracao"),
                "signatarios": doc.get("signatarios"),
                "fonte": doc.get("source"),
                "confianca": doc.get("confianca"),
                "conteudo": trecho,
                "leitura": {
                    "offset": offset,
                    "chars_retornados": chars_retornados,
                    "tamanho_total": tamanho_total,
                    "tem_mais": tem_mais,
                    "proximo_offset": offset + chars_retornados if tem_mais else None,
                },
            }

            try:
                get_usage_logger().log_tool_call(
                    tool_name="read_documento",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    params={
                        "document_id": document_id,
                        "max_chars": max_chars,
                        "offset": offset,
                    },
                )
            except Exception:
                pass
            return resultado
        except Exception as e:
            try:
                get_usage_logger().log_tool_call(
                    tool_name="read_documento",
                    duration_ms=ms_since(_start),
                    user_id=user_id,
                    user_email=user_email,
                    success=False,
                    error_message=str(e),
                )
            except Exception:
                pass
            return erro_instrutivo(str(e), "Verifique o document_id.")
