"""Cliente Supabase com suporte a RLS baseado em user_id."""

import os
import unicodedata
from typing import Any, ClassVar

from supabase import Client, create_client


def _normalize_text(text: str) -> str:
    """Remove acentos para busca normalizada."""
    normalized = unicodedata.normalize("NFD", text)
    return "".join(c for c in normalized if unicodedata.category(c) != "Mn").lower()


# Mapeamento de codigos genericos TipoProcessoDocumento#XX para tipos reais
MAPA_TIPOS_CODIGO: dict[str, str] = {
    "TipoProcessoDocumento#2": "Atos Ordinatorios",
    "TipoProcessoDocumento#5": "Cartas",
    "TipoProcessoDocumento#6": "Despachos",
    "TipoProcessoDocumento#7": "Oficios",
    "TipoProcessoDocumento#13": "Certidoes da Secretaria",
    "TipoProcessoDocumento#14": "Audiencias",
    "TipoProcessoDocumento#15": "Decisoes Interlocutorias",
    "TipoProcessoDocumento#20": "Denuncia",
    "TipoProcessoDocumento#21": "Cartas Precatorias",
    "TipoProcessoDocumento#69": "Auto de Prisao em Flagrante",
    "TipoProcessoDocumento#71": "Aviso de Recebimento (AR)",
    "TipoProcessoDocumento#73": "Carta Precatoria/Rogatoria",
    "TipoProcessoDocumento#108": "Peticao de Acompanhamento",
    "TipoProcessoDocumento#152": "Mandado de Citacao",
    "TipoProcessoDocumento#154": "Mandado",
    "TipoProcessoDocumento#155": "Mandado de Intimacao",
    "TipoProcessoDocumento#249": "Retorno de Carta Precatoria",
    "TipoProcessoDocumento#532": "Certidao de Realizacao de Expediente",
    "TipoProcessoDocumento#9500": "Peticao",
    "TipoProcessoDocumento#9501": "Procuracao/Substabelecimento",
}


class SupabaseClient:
    """Cliente Supabase para acesso ao banco de dados TecJustica."""

    def __init__(
        self,
        url: str | None = None,
        service_key: str | None = None,
        user_id: str | None = None,
    ):
        self.url = url or os.getenv("SUPABASE_URL")
        self.service_key = service_key or os.getenv("SUPABASE_SERVICE_KEY")
        self.user_id = user_id

        if not self.url:
            raise ValueError("SUPABASE_URL is required")
        if not self.service_key:
            raise ValueError("SUPABASE_SERVICE_KEY is required")

        self._client: Client = create_client(self.url, self.service_key)

    @property
    def client(self) -> Client:
        """Retorna o cliente Supabase."""
        return self._client

    # ==================== PROCESSOS ====================

    async def buscar_processo(self, numero_processo: str) -> dict[str, Any] | None:
        """Busca processo por numero."""
        try:
            # Usa .limit(1) em vez de .maybe_single() para evitar bug do
            # postgrest-py "Missing response" code 204.
            response = (
                self._client.table("processos")
                .select("*")
                .eq("numero_processo", numero_processo)
                .limit(1)
                .execute()
            )
            dados = response.data or []
            return dados[0] if dados else None
        except Exception:
            return None

    async def listar_processos_usuario(self, user_id: str) -> list[dict[str, Any]]:
        """Lista processos do usuario (via jobs)."""
        try:
            response = (
                self._client.table("jobs")
                .select("*, processos(*)")
                .eq("user_id", user_id)
                .eq("status", "completed")
                .execute()
            )
            return response.data or [] if response else []
        except Exception:
            return []

    async def listar_todos_processos(self, limite: int = 20) -> list[dict[str, Any]]:
        """Lista todos os processos do sistema (sem filtro de usuario)."""
        try:
            response = (
                self._client.table("jobs")
                .select("*, processos(*)")
                .eq("status", "completed")
                .limit(limite)
                .execute()
            )
            return response.data or [] if response else []
        except Exception:
            return []

    async def buscar_por_parte(self, nome_parte: str) -> list[dict[str, Any]]:
        """Busca processos por nome de parte (busca em JSONB)."""
        try:
            response = self._client.rpc(
                "buscar_processos_por_parte", {"p_nome": nome_parte}
            ).execute()
            return response.data or [] if response else []
        except Exception:
            return []

    async def processo_completo(self, numero_processo: str) -> dict[str, Any] | None:
        """Busca processo completo via view."""
        try:
            # Usa .limit(1) em vez de .maybe_single() para evitar bug do
            # postgrest-py "Missing response" code 204.
            response = (
                self._client.table("vw_processo_completo")
                .select("*")
                .eq("numero_processo", numero_processo)
                .limit(1)
                .execute()
            )
            dados = response.data or []
            return dados[0] if dados else None
        except Exception:
            # Fallback para tabela principal se view falhar
            return await self.buscar_processo(numero_processo)

    async def stats_processo(self, numero_processo: str) -> dict[str, Any] | None:
        """Busca estatisticas do processo."""
        try:
            # Usa .limit(1) em vez de .maybe_single() para evitar bug do
            # postgrest-py "Missing response" code 204.
            response = (
                self._client.table("vw_stats_processo")
                .select("*")
                .eq("numero_processo", numero_processo)
                .limit(1)
                .execute()
            )
            dados = response.data or []
            return dados[0] if dados else None
        except Exception:
            return None

    # ==================== DOCUMENTOS ====================

    async def listar_documentos(
        self,
        numero_processo: str,
        limite: int = 100,
        ordenar_por: str = "sequencia",
        incluir_preview: bool = False,
    ) -> list[dict[str, Any]]:
        """Lista documentos de um processo."""
        campos = "id, sequencia, nome, tipo_nome, data_juntada, tamanho_texto, paginas"
        if incluir_preview:
            campos += ", conteudo"

        query = (
            self._client.table("documents").select(campos).eq("numero_processo", numero_processo)
        )

        # Ordenação
        if ordenar_por == "data":
            query = query.order("data_juntada", desc=True)
        elif ordenar_por == "tipo":
            query = query.order("tipo_nome").order("sequencia", desc=True)
        else:
            query = query.order("sequencia", desc=True)

        response = query.limit(limite).execute()
        return response.data or []

    async def ler_documento(self, document_id: str) -> dict[str, Any] | None:
        """Le documento completo por ID."""
        # Usa .limit(1) em vez de .maybe_single() para evitar bug do
        # postgrest-py "Missing response" code 204.
        response = (
            self._client.table("documents").select("*").eq("id", document_id).limit(1).execute()
        )
        dados = response.data or []
        return dados[0] if dados else None

    async def preview_documentos(
        self,
        numero_processo: str,
        limite: int = 20,
    ) -> list[dict[str, Any]]:
        """Lista preview de documentos."""
        response = (
            self._client.table("vw_documentos_preview")
            .select("*")
            .eq("numero_processo", numero_processo)
            .limit(limite)
            .execute()
        )
        return response.data or []

    async def buscar_em_documentos(
        self,
        termo: str,
        numero_processo: str | None = None,
        limite: int = 20,
        incluir_conteudo: bool = False,
    ) -> list[dict[str, Any]]:
        """Busca termo no conteudo dos documentos."""
        campos = "id, numero_processo, tipo_nome, nome, sequencia, data_juntada"
        if incluir_conteudo:
            campos += ", conteudo"

        query = (
            self._client.table("documents")
            .select(campos)
            .ilike("conteudo", f"%{termo}%")
            .limit(limite)
        )

        if numero_processo:
            query = query.eq("numero_processo", numero_processo)

        response = query.execute()
        return response.data or []

    async def buscar_documentos_por_periodo(
        self,
        numero_processo: str,
        data_inicio: str | None = None,
        data_fim: str | None = None,
        limite: int = 50,
    ) -> list[dict[str, Any]]:
        """Busca documentos juntados em um periodo especifico."""
        query = (
            self._client.table("documents")
            .select("id, sequencia, nome, tipo_nome, data_juntada, tamanho_texto, conteudo")
            .eq("numero_processo", numero_processo)
            .order("data_juntada", desc=True)
            .limit(limite)
        )
        if data_inicio:
            query = query.gte("data_juntada", data_inicio)
        if data_fim:
            query = query.lte("data_juntada", data_fim)

        response = query.execute()
        return response.data or []

    async def buscar_documentos_por_tamanho(
        self,
        numero_processo: str,
        min_chars: int = 1000,
        max_chars: int | None = None,
        limite: int = 30,
    ) -> list[dict[str, Any]]:
        """Busca documentos por faixa de tamanho."""
        query = (
            self._client.table("documents")
            .select("id, sequencia, nome, tipo_nome, data_juntada, tamanho_texto, conteudo")
            .eq("numero_processo", numero_processo)
            .gte("tamanho_texto", min_chars)
            .order("tamanho_texto", desc=True)
            .limit(limite)
        )
        if max_chars:
            query = query.lte("tamanho_texto", max_chars)

        response = query.execute()
        return response.data or []

    async def buscar_documentos_relevantes(
        self,
        numero_processo: str,
        limite: int = 50,
    ) -> list[dict[str, Any]]:
        """Busca documentos usando view otimizada com prioridade."""
        response = (
            self._client.table("vw_documentos_relevantes")
            .select("*")
            .eq("numero_processo", numero_processo)
            .limit(limite)
            .execute()
        )
        return response.data or []

    # ==================== MOVIMENTACOES ====================

    async def timeline_processo(
        self,
        numero_processo: str,
        limite: int = 100,
    ) -> list[dict[str, Any]]:
        """Busca timeline unificada do processo."""
        response = (
            self._client.table("vw_timeline_processo")
            .select("*")
            .eq("numero_processo", numero_processo)
            .order("data", desc=True)
            .limit(limite)
            .execute()
        )
        return response.data or []

    async def movimentacoes_recentes(
        self,
        limite: int = 50,
        _user_id: str | None = None,  # Reservado para filtro RLS futuro
    ) -> list[dict[str, Any]]:
        """Busca movimentacoes recentes."""
        query = (
            self._client.table("vw_movimentacoes_recentes")
            .select("*")
            .order("data_hora", desc=True)
            .limit(limite)
        )

        response = query.execute()
        return response.data or []

    async def listar_movimentacoes(
        self,
        numero_processo: str,
        tipo: str | None = None,
        limite: int = 100,
    ) -> list[dict[str, Any]]:
        """Lista movimentacoes de um processo."""
        query = (
            self._client.table("movimentacoes")
            .select("*")
            .eq("numero_processo", numero_processo)
            .order("data_hora", desc=True)
            .limit(limite)
        )

        if tipo:
            query = query.eq("tipo_nome", tipo)

        response = query.execute()
        return response.data or []

    async def buscar_movimentacoes_por_termo(
        self,
        numero_processo: str,
        termo: str,
        limite: int = 20,
    ) -> list[dict[str, Any]]:
        """Busca movimentacoes por termo usando ilike no banco (otimizado)."""
        # Usa ilike para buscar no banco ao inves de filtrar em memoria
        response = (
            self._client.table("movimentacoes")
            .select("*")
            .eq("numero_processo", numero_processo)
            .ilike("descricao", f"%{termo}%")
            .order("data_hora", desc=True)
            .limit(limite)
            .execute()
        )
        return response.data or []

    async def tipos_movimentacoes_agregados(
        self,
        numero_processo: str,
    ) -> dict[str, int]:
        """
        Retorna contagem de movimentacoes por tipo.

        Usa agregacao limitada para evitar carregar muitos registros.
        """
        # Busca apenas os campos necessarios com limite razoavel
        response = (
            self._client.table("movimentacoes")
            .select("tipo_nome")
            .eq("numero_processo", numero_processo)
            .limit(500)
            .execute()
        )

        movs = response.data or []

        # Agrupa por tipo
        tipos: dict[str, int] = {}
        for mov in movs:
            tipo = mov.get("tipo_nome") or "Nao classificado"
            tipos[tipo] = tipos.get(tipo, 0) + 1

        return dict(sorted(tipos.items(), key=lambda x: x[1], reverse=True))

    async def tipos_documentos_agregados(
        self,
        numero_processo: str,
    ) -> dict[str, int]:
        """
        Retorna contagem de documentos por tipo.

        Usa agregacao limitada para evitar carregar muitos registros.
        """
        # Busca apenas os campos necessarios
        response = (
            self._client.table("documents")
            .select("tipo_nome, tipo")
            .eq("numero_processo", numero_processo)
            .limit(500)
            .execute()
        )

        docs = response.data or []

        # Agrupa por tipo
        tipos: dict[str, int] = {}
        for doc in docs:
            tipo = doc.get("tipo_nome") or doc.get("tipo") or "Nao classificado"
            tipos[tipo] = tipos.get(tipo, 0) + 1

        return dict(sorted(tipos.items(), key=lambda x: x[1], reverse=True))

    # ==================== MARKDOWN E SELECAO INTELIGENTE ====================

    async def obter_markdown_processo(self, numero_processo: str) -> dict[str, Any] | None:
        """Busca campo markdown da tabela jobs."""
        try:
            # Usa .limit(1) em vez de .maybe_single() para evitar bug do
            # postgrest-py "Missing response" code 204.
            response = (
                self._client.table("jobs")
                .select("numero_processo, markdown")
                .eq("numero_processo", numero_processo)
                .eq("status", "completed")
                .limit(1)
                .execute()
            )
            dados = response.data or []
            return dados[0] if dados else None
        except Exception:
            return None

    async def listar_metadados_documentos(self, numero_processo: str) -> list[dict[str, Any]]:
        """Lista metadados dos documentos SEM conteudo."""
        response = (
            self._client.table("documents")
            .select("id, sequencia, nome, tipo_nome, data_juntada, tamanho_texto")
            .eq("numero_processo", numero_processo)
            .order("sequencia", desc=True)
            .execute()
        )
        return response.data or []

    async def buscar_documentos_por_nomes(
        self, numero_processo: str, nomes: list[str]
    ) -> list[dict[str, Any]]:
        """Busca docs por nome com normalizacao de acentos."""
        response = (
            self._client.table("documents")
            .select("id, sequencia, nome, tipo_nome, data_juntada, conteudo")
            .eq("numero_processo", numero_processo)
            .order("sequencia", desc=True)
            .execute()
        )
        todos = response.data or []
        termos = [_normalize_text(n) for n in nomes]

        encontrados = []
        for doc in todos:
            nome_norm = _normalize_text(doc.get("nome") or "")
            for termo in termos:
                if termo in nome_norm:
                    encontrados.append(doc)
                    break
        return encontrados

    async def selecionar_documentos_inteligente(
        self, numero_processo: str, max_chars: int = 7_000_000
    ) -> list[dict[str, Any]]:
        """Seleciona documentos priorizando por tipo ate atingir max_chars."""
        prioridade_nomes = [
            "sentenca",
            "acordao",
            "decisao",
            "denuncia",
            "peticao_inicial",
            "peticao inicial",
            "contestacao",
            "despacho",
            "certid",
        ]

        response = (
            self._client.table("documents")
            .select("id, sequencia, nome, tipo_nome, data_juntada, tamanho_texto, conteudo")
            .eq("numero_processo", numero_processo)
            .order("sequencia", desc=True)
            .execute()
        )
        todos_docs = response.data or []

        def prioridade_doc(doc: dict[str, Any]) -> int:
            nome = _normalize_text(doc.get("nome") or "")
            for i, termo in enumerate(prioridade_nomes):
                if termo in nome:
                    return i
            return 999

        docs_ordenados = sorted(todos_docs, key=prioridade_doc)

        selecionados = []
        chars_total = 0
        for doc in docs_ordenados:
            tamanho = doc.get("tamanho_texto") or len(doc.get("conteudo") or "")
            if chars_total + tamanho > max_chars:
                continue
            selecionados.append(doc)
            chars_total += tamanho

        return selecionados

    # ==================== DETECCAO DE TIPO DE PROCESSO ====================

    # Termos que indicam processo criminal
    TERMOS_CRIMINAL: ClassVar[list[str]] = [
        "penal",
        "criminal",
        "crime",
        "inquerito",
        "acao penal",
        "execucao penal",
        "habeas corpus",
        "prisao",
        "denuncia",
        "queixa-crime",
        "reu preso",
        "flagrante",
        "liberdade provisoria",
        "medida cautelar",
        "audiencia de custodia",
    ]

    async def detectar_tipo_processo(self, numero_processo: str) -> str:
        """
        Detecta automaticamente se o processo e civel ou criminal.

        Analisa a classe do processo para determinar o tipo.
        Retorna 'criminal' ou 'civel' (default).

        Args:
            numero_processo: Numero do processo no formato CNJ

        Returns:
            'criminal' ou 'civel'
        """
        processo = await self.processo_completo(numero_processo)
        if not processo:
            return "civel"  # Default

        # Verifica classe do processo
        classe = (processo.get("classe_descricao") or "").lower()

        # Verifica assuntos
        assuntos = processo.get("assuntos") or []
        assuntos_str = " ".join((a.get("nome") or "").lower() for a in assuntos)

        # Combina classe + assuntos para analise
        texto_analise = f"{classe} {assuntos_str}"

        if any(termo in texto_analise for termo in self.TERMOS_CRIMINAL):
            return "criminal"

        return "civel"

    # ==================== CONTEXTO PARA LLM ====================

    # Prioridade de tipos de documentos para processos CIVEIS
    PRIORIDADE_TIPOS_DOC_CIVEL: ClassVar[list[str]] = [
        "Sentença",
        "Acórdão",
        "Decisão",
        "Despacho",
        "Petição Inicial",
        "Contestação",
        "Réplica",
        "Agravo",
        "Recurso",
        "Embargos",
        "Mandado",
        "Certidão",
        "Laudo Pericial",
        "Ata de Audiência",
    ]

    # Prioridade de tipos de documentos para processos CRIMINAIS
    PRIORIDADE_TIPOS_DOC_CRIMINAL: ClassVar[list[str]] = [
        "Sentença",
        "Acórdão",
        "Decisão",
        "Denúncia",
        "Queixa",
        "Queixa-Crime",
        "Defesa Técnica",
        "Defesa Prévia",
        "Resposta à Acusação",
        "Alegações Finais",
        "Inquérito Policial",
        "Relatório de Investigação",
        "Parecer do MP",
        "Parecer do Ministério Público",
        "Habeas Corpus",
        "Liberdade Provisória",
        "Mandado de Prisão",
        "Alvará de Soltura",
        "Ata de Audiência",
        "Laudo Pericial",
        "Auto de Prisão em Flagrante",
    ]

    # Alias para compatibilidade (usa civel como padrao)
    PRIORIDADE_TIPOS_DOC: list[str] = PRIORIDADE_TIPOS_DOC_CIVEL

    async def obter_contexto_processo(
        self,
        numero_processo: str,
        incluir_docs: bool = True,
        incluir_movs: bool = True,
        max_chars: int = 200000,
        max_chars_por_doc: int = 8000,
    ) -> str:
        """
        Obtem contexto otimizado do processo para enviar ao LLM.

        Otimizacoes:
        - Selecao inteligente de documentos por tipo/relevancia
        - Limite de chars por documento
        - Limite global de contexto (default: 200k chars ~50k tokens)

        Args:
            numero_processo: Numero do processo
            incluir_docs: Incluir conteudo dos documentos
            incluir_movs: Incluir movimentacoes
            max_chars: Limite total de caracteres (default: 200k)
            max_chars_por_doc: Limite por documento (default: 8k)

        Returns:
            String formatada com contexto do processo
        """
        contexto_parts = []
        chars_usados = 0

        # 1. Dados basicos do processo
        processo = await self.processo_completo(numero_processo)
        if processo:
            proc_text = self._formatar_processo(processo)
            contexto_parts.append(proc_text)
            chars_usados += len(proc_text)

        # 2. Estatisticas
        stats = await self.stats_processo(numero_processo)
        if stats:
            stats_text = self._formatar_stats(stats)
            contexto_parts.append(stats_text)
            chars_usados += len(stats_text)

        # 3. Movimentacoes (limitado a 30 mais recentes)
        if incluir_movs and chars_usados < max_chars:
            movs = await self.listar_movimentacoes(numero_processo, limite=30)
            if movs:
                movs_text = self._formatar_movimentacoes(movs)
                if chars_usados + len(movs_text) <= max_chars:
                    contexto_parts.append(movs_text)
                    chars_usados += len(movs_text)

        # 4. Documentos com selecao inteligente
        if incluir_docs and chars_usados < max_chars:
            docs = await self._selecionar_docs_relevantes(numero_processo, limite=15)
            for doc in docs:
                if doc.get("conteudo"):
                    doc_text = self._formatar_documento(doc, max_chars=max_chars_por_doc)
                    if chars_usados + len(doc_text) <= max_chars:
                        contexto_parts.append(doc_text)
                        chars_usados += len(doc_text)
                    else:
                        break

        return "\n\n---\n\n".join(contexto_parts)

    async def _selecionar_docs_relevantes(
        self,
        numero_processo: str,
        limite: int = 15,
        tipo_processo: str | None = None,
    ) -> list[dict[str, Any]]:
        """
        Seleciona documentos mais relevantes por tipo e data.

        Prioriza documentos baseado no tipo de processo:
        - Civel: Sentenca > Decisao > Peticao Inicial > Contestacao > outros
        - Criminal: Sentenca > Denuncia > Defesa Tecnica > Inquerito > outros

        Args:
            numero_processo: Numero do processo
            limite: Quantidade maxima de documentos
            tipo_processo: 'civel' ou 'criminal' (auto-detecta se None)
        """
        docs = await self.listar_documentos(numero_processo, limite=50)

        if not docs:
            return []

        # Detecta tipo de processo se nao fornecido
        if tipo_processo is None:
            tipo_processo = await self.detectar_tipo_processo(numero_processo)

        # Seleciona lista de prioridade baseada no tipo
        lista_prioridade = (
            self.PRIORIDADE_TIPOS_DOC_CRIMINAL
            if tipo_processo == "criminal"
            else self.PRIORIDADE_TIPOS_DOC_CIVEL
        )

        def prioridade_doc(doc: dict[str, Any]) -> tuple[int, str]:
            tipo = doc.get("tipo_nome") or doc.get("tipo") or ""
            # Busca prioridade do tipo na lista apropriada
            try:
                idx = lista_prioridade.index(tipo)
            except ValueError:
                idx = 999

            # Data para ordenacao secundaria (mais recentes primeiro)
            data = doc.get("data_juntada") or "2000-01-01"
            return (idx, data)

        # Ordena por prioridade de tipo, depois por data (mais recentes)
        docs_ordenados = sorted(docs, key=prioridade_doc)

        return docs_ordenados[:limite]

    def _formatar_processo(self, processo: dict[str, Any]) -> str:
        """Formata dados do processo para contexto."""
        partes = processo.get("partes", [])
        assuntos = processo.get("assuntos", [])

        partes_str = ""
        if partes:
            partes_str = "\n".join(
                f"  - {p.get('nome', 'N/A')} ({p.get('tipo', 'N/A')})" for p in partes[:10]
            )

        assuntos_str = ""
        if assuntos:
            assuntos_str = ", ".join(a.get("nome", "N/A") for a in assuntos[:5])

        return f"""## PROCESSO: {processo.get("numero_processo", "N/A")}

**Tribunal:** {processo.get("sigla_tribunal", "N/A")}
**Classe:** {processo.get("classe_descricao", "N/A")}
**Instancia:** {processo.get("instancia", "N/A")}
**Natureza:** {processo.get("natureza", "N/A")}
**Valor da Causa:** R$ {(processo.get("valor_causa") or 0):,.2f}
**Data Ajuizamento:** {processo.get("data_ajuizamento", "N/A")}
**Justica Gratuita:** {"Sim" if processo.get("justica_gratuita") else "Nao"}

**Partes:**
{partes_str}

**Assuntos:** {assuntos_str}

**Total Documentos:** {processo.get("total_docs", 0)}
**Total Movimentacoes:** {processo.get("total_movs", 0)}
"""

    def _formatar_stats(self, stats: dict[str, Any]) -> str:
        """Formata estatisticas do processo."""
        return f"""## ESTATISTICAS

- Documentos extraidos: {stats.get("docs_extraidos") or 0}
- Movimentacoes sincronizadas: {stats.get("movs_sincronizados") or 0}
- Tamanho total: {(stats.get("tamanho_total_bytes") or 0) / 1024 / 1024:.2f} MB
- Media de paginas: {stats.get("media_paginas") or 0}
- Documentos sigilosos: {stats.get("docs_sigilosos") or 0}
- Documentos via OCR: {stats.get("docs_ocr") or 0}
"""

    def _formatar_movimentacoes(self, movs: list[dict[str, Any]]) -> str:
        """Formata movimentacoes para contexto."""
        items = []
        for mov in movs[:30]:
            items.append(f"- [{mov.get('data_hora', 'N/A')}] {mov.get('descricao', 'N/A')}")
        return "## MOVIMENTACOES RECENTES\n\n" + "\n".join(items)

    def _formatar_documento(self, doc: dict[str, Any], max_chars: int = 8000) -> str:
        """Formata documento para contexto com limite de tamanho."""
        conteudo_original = doc.get("conteudo", "")
        conteudo = conteudo_original[:max_chars]
        truncado = len(conteudo_original) > max_chars

        truncado_info = f" (truncado de {len(conteudo_original)} chars)" if truncado else ""

        return f"""## DOCUMENTO: {doc.get("nome", doc.get("tipo_nome", "N/A"))}

**Tipo:** {doc.get("tipo_nome", "N/A")}
**Data Juntada:** {doc.get("data_juntada", "N/A")}
**Paginas:** {doc.get("paginas", "N/A")}{truncado_info}

**Conteudo:**
{conteudo}
"""


_instance: SupabaseClient | None = None


def get_supabase_client(user_id: str | None = None) -> SupabaseClient:
    """Factory singleton para cliente Supabase."""
    global _instance
    if _instance is None:
        _instance = SupabaseClient(user_id=user_id)
    return _instance
