import type { ContextoDoAssistente } from "../contexto";
import { AVISO_DE_DADOS, dinheiro, exigir, falha, hojeEmBrasilia, inicioDoDia, nomeDoCliente, periodoBr, pode, recusar, somarDias, todasAsLinhas, diasEntre } from "./comum";
import type { FerramentaDeLeitura } from "./tipos";

/* O dinheiro que entrou: os pagamentos registrados, sem os estornados. */
export async function recebidoNoPeriodo(ctx: ContextoDoAssistente, de: string, ate: string) {
  const { linhas, cortado } = await todasAsLinhas<{ amount: number | null }>(
    (inicio, fim) =>
      ctx.supabase
        .from("payments")
        .select("amount")
        .in("user_id", ctx.tenantIds)
        .is("reversed_at", null)
        .gte("payment_date", inicioDoDia(de))
        .lt("payment_date", inicioDoDia(somarDias(ate, 1)))
        .order("payment_date", { ascending: false })
        .range(inicio, fim),
    "somar o que foi recebido"
  );
  return { total: dinheiro(linhas.reduce((soma, linha) => soma + Number(linha.amount || 0), 0)), pagamentos: linhas.length, cortado };
}

type PedidoEmAberto = {
  id: string;
  number: number | null;
  total_amount: number | null;
  paid_amount: number | null;
  created_at: string | null;
  client_id: string | null;
};

/* O que os clientes ainda devem: pedidos não cancelados e não quitados. É a
   mesma conta do "A receber" do painel.

   A soma lê só os números de cada pedido, e no máximo cinco mil deles. O nome
   do cliente é buscado depois, só para os dez maiores: é a lista que aparece,
   e carregar o cliente de milhares de pedidos para mostrar dez seria peso à
   toa numa consulta que o assistente repete várias vezes por conversa. */
export async function aReceberDePedidos(ctx: ContextoDoAssistente) {
  const { linhas, cortado } = await todasAsLinhas<PedidoEmAberto>(
    (inicio, fim) =>
      ctx.supabase
        .from("orders")
        .select("id,number,total_amount,paid_amount,created_at,client_id")
        .in("user_id", ctx.tenantIds)
        .neq("status", "canceled")
        .neq("financial_status", "paid")
        .order("created_at", { ascending: false })
        .range(inicio, fim),
    "somar o que está a receber",
    5000
  );
  const abertos = linhas
    .map((pedido) => ({
      pedido_id: pedido.id,
      numero: pedido.number,
      cliente_id: pedido.client_id,
      em_aberto: dinheiro(Number(pedido.total_amount || 0) - Number(pedido.paid_amount || 0)),
      criado_em: pedido.created_at,
    }))
    .filter((pedido) => pedido.em_aberto > 0);
  const maiores = [...abertos].sort((a, b) => b.em_aberto - a.em_aberto).slice(0, 10);

  const idsDosClientes = [...new Set(maiores.map((pedido) => pedido.cliente_id).filter((id): id is string => Boolean(id)))];
  const nomes = new Map<string, string | null>();
  if (idsDosClientes.length) {
    const { data, error } = await ctx.supabase.from("clients").select("id,name,company_name").in("id", idsDosClientes).in("user_id", ctx.tenantIds);
    if (error) falha("ler os clientes dos pedidos em aberto", error);
    for (const cliente of (data ?? []) as Array<{ id: string; name: string | null; company_name: string | null }>) {
      nomes.set(cliente.id, nomeDoCliente(cliente));
    }
  }

  return {
    total: dinheiro(abertos.reduce((soma, pedido) => soma + pedido.em_aberto, 0)),
    pedidos: abertos.length,
    maiores: maiores.map(({ cliente_id, ...pedido }) => ({ ...pedido, cliente: cliente_id ? nomes.get(cliente_id) ?? null : null })),
    cortado,
  };
}

type ContaAPagar = {
  id: string;
  description: string | null;
  supplier_name: string | null;
  amount: number | null;
  due_date: string | null;
  status: string | null;
};

export const resumoFinanceiro: FerramentaDeLeitura = {
  nome: "resumo_financeiro",
  titulo: "Resumo financeiro",
  descricao:
    "Mostra o dinheiro de um período: quanto entrou (pagamentos recebidos), quanto os clientes ainda devem (pedidos em aberto, com os maiores) e as contas a pagar em aberto até o fim do período, com as vencidas. Sem datas, mostra o mês atual. Só para quem pode ver o financeiro.",
  campos: {
    de: { tipo: "data", descricao: "Primeiro dia do período. O padrão é o dia 1 do mês atual." },
    ate: { tipo: "data", descricao: "Último dia do período. O padrão é hoje." },
  },
  tipo: "leitura",
  permissoes: ["financial.overview.read"],
  async executar(ctx, args) {
    exigir(ctx, "financial.overview.read", "ver o financeiro");
    if (!pode(ctx, "financial.amounts.see")) exigir(ctx, "financial.amounts.see", "ver os valores do financeiro");

    const hoje = hojeEmBrasilia();
    const de = (args.de as string | undefined) ?? `${hoje.slice(0, 8)}01`;
    const ate = (args.ate as string | undefined) ?? hoje;
    if (ate < de) recusar("O fim do período é antes do início.");
    if (diasEntre(de, ate) > 370) recusar("Período grande demais. Peça no máximo um ano de cada vez.");

    const [recebido, aReceber, contas] = await Promise.all([
      recebidoNoPeriodo(ctx, de, ate),
      aReceberDePedidos(ctx),
      /* A trava de contas a pagar confere uma marca da empresa que a sessão
         do assistente não carrega (lia zero): lê com a chave do servidor,
         presa à empresa desta conta (a mesma régua de financeiro-completo),
         depois da permissão conferida acima. */
      ctx.admin
        .from("accounts_payable")
        .select("id,description,supplier_name,amount,due_date,status")
        .eq("company_id", ctx.empresa.companyId)
        .in("status", ["PENDING", "OVERDUE"])
        .lte("due_date", ate)
        .order("due_date", { ascending: true })
        .limit(500),
    ]);
    if (contas.error) falha("ler as contas a pagar", contas.error);

    const aPagar = ((contas.data ?? []) as ContaAPagar[]).map((conta) => ({
      id: conta.id,
      descricao: conta.description || "Conta a pagar",
      fornecedor: conta.supplier_name || null,
      valor: dinheiro(conta.amount),
      vence_em: conta.due_date,
      vencida: Boolean(conta.due_date && conta.due_date < hoje),
    }));
    const vencidas = aPagar.filter((conta) => conta.vencida);

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        periodo: { de, ate },
        recebido_no_periodo: { total: recebido.total, pagamentos: recebido.pagamentos },
        a_receber_de_pedidos: { total: aReceber.total, pedidos: aReceber.pedidos, maiores: aReceber.maiores },
        a_pagar_ate_o_fim_do_periodo: {
          total: dinheiro(aPagar.reduce((soma, conta) => soma + conta.valor, 0)),
          contas: aPagar.length,
          vencidas: { total: dinheiro(vencidas.reduce((soma, conta) => soma + conta.valor, 0)), contas: vencidas.length },
          proximas: aPagar.slice(0, 15),
        },
        ...(recebido.cortado || aReceber.cortado ? { atencao: "Há mais lançamentos do que foi possível somar de uma vez. Os totais podem estar abaixo do real; confira no painel." } : {}),
      },
      resumo: `Consultou o resumo financeiro ${periodoBr(de, ate)}`,
    };
  },
};
