import { getLiquidationCompatibilityError } from "@/lib/liquidation-accounts";

import type { ContextoDoAssistente } from "../contexto";
import { dinheiro, falha, limparBusca, pode, recusar } from "./comum";
import { escolherPorNome, nomeDaForma, nomeDoTipoDeConta, type FormaDePagamento } from "./financeiro-completo-apoio";

/* O que as ferramentas do financeiro dividem: achar a conta, a categoria e o
   fornecedor pelo nome que a pessoa disse, e saber se ela pode ver valores.

   As tabelas do financeiro são lidas com a chave de serviço, sempre presas ao
   `company_id` da empresa e depois da permissão conferida — é como as telas
   do painel leem (as travas dessas tabelas perguntam por uma informação que o
   acesso da pessoa não carrega, e a leitura voltaria vazia). */

export type ContaBancaria = {
  id: string;
  name: string | null;
  bank_name: string | null;
  type: string | null;
  active: boolean | null;
  initial_balance: number | null;
};

export function empresaDe(ctx: ContextoDoAssistente) {
  return ctx.empresa.companyId;
}

/** Quem não pode ver valores do financeiro trabalha com eles ocultos. */
export function podeVerValores(ctx: ContextoDoAssistente) {
  return pode(ctx, "financial.amounts.see");
}

export const VALORES_OCULTOS =
  "Esta pessoa não tem permissão para ver os valores em dinheiro do financeiro: eles aparecem como null. Quem libera é o dono da empresa, em Funcionários.";

export function nomeDaConta(conta: Pick<ContaBancaria, "name" | "bank_name" | "type"> | null | undefined) {
  if (!conta) return null;
  return conta.name?.trim() || conta.bank_name?.trim() || (String(conta.type).toUpperCase() === "CAIXA" ? "Caixa físico" : "Conta bancária");
}

export async function contasBancarias(ctx: ContextoDoAssistente): Promise<ContaBancaria[]> {
  const { data, error } = await ctx.admin
    .from("bank_accounts")
    .select("id,name,bank_name,type,active,initial_balance")
    .eq("company_id", empresaDe(ctx))
    .order("name", { ascending: true });
  if (error) falha("ler as contas bancárias", error);
  return (data ?? []) as ContaBancaria[];
}

/** O saldo de cada conta, como a tela de contas bancárias mostra. */
export async function saldosDasContas(ctx: ContextoDoAssistente, contas: ContaBancaria[]) {
  const { data, error } = await ctx.admin
    .from("v_bank_account_balance")
    .select("bank_account_id,current_balance")
    .eq("company_id", empresaDe(ctx));
  if (error) falha("ler os saldos das contas", error);
  const saldos = new Map<string, number>();
  for (const linha of (data ?? []) as Array<{ bank_account_id: string; current_balance: number | null }>) {
    saldos.set(linha.bank_account_id, dinheiro(linha.current_balance));
  }
  for (const conta of contas) {
    if (!saldos.has(conta.id)) saldos.set(conta.id, dinheiro(conta.initial_balance));
  }
  return saldos;
}

function listaDeNomes(contas: ContaBancaria[]) {
  return contas.map((conta) => `${nomeDaConta(conta)} (${nomeDoTipoDeConta(conta.type)})`).join(", ");
}

/**
 * A conta onde o dinheiro entra ou sai. Pelo nome ou id que a pessoa disse;
 * sem nada dito, a conta padrão do lançamento (se combinar com a forma de
 * pagamento) ou a única conta ativa que combina. Mais de uma possível: pede
 * para a pessoa escolher, com a lista.
 */
export async function escolherConta(
  ctx: ContextoDoAssistente,
  opcoes: { dito?: unknown; forma?: FormaDePagamento | null; padraoId?: string | null; papel: string; podeFicarSem?: boolean }
): Promise<ContaBancaria | null> {
  const todas = await contasBancarias(ctx);
  const ativas = todas.filter((conta) => conta.active !== false);
  const compativeis = opcoes.forma ? ativas.filter((conta) => !getLiquidationCompatibilityError(opcoes.forma, conta)) : ativas;

  if (opcoes.dito !== undefined && opcoes.dito !== null && String(opcoes.dito).trim()) {
    const escolha = escolherPorNome(todas, opcoes.dito, (conta) => [conta.name, conta.bank_name]);
    if (escolha.tipo === "nenhum") {
      recusar(`Não achei a conta "${String(opcoes.dito)}". As contas da empresa são: ${listaDeNomes(ativas) || "nenhuma cadastrada"}.`);
    }
    if (escolha.tipo === "varios") {
      recusar(`Mais de uma conta combina com "${String(opcoes.dito)}": ${listaDeNomes(escolha.itens)}. Diga qual.`);
    }
    const conta = escolha.item;
    if (conta.active === false) recusar(`A conta ${nomeDaConta(conta)} está bloqueada. Escolha outra: ${listaDeNomes(ativas)}.`);
    if (opcoes.forma) {
      const erro = getLiquidationCompatibilityError(opcoes.forma, conta);
      if (erro) {
        recusar(
          opcoes.forma === "cash"
            ? `Dinheiro em espécie entra e sai pelo caixa físico, e ${nomeDaConta(conta)} não é um caixa. ${compativeis.length ? `Caixas: ${listaDeNomes(compativeis)}.` : ""}`
            : `${nomeDaForma(opcoes.forma)} passa por conta bancária, não pelo caixa físico. ${compativeis.length ? `Contas que aceitam: ${listaDeNomes(compativeis)}.` : ""}`
        );
      }
    }
    return conta;
  }

  if (opcoes.padraoId) {
    const padrao = compativeis.find((conta) => conta.id === opcoes.padraoId);
    if (padrao) return padrao;
  }
  if (compativeis.length === 1) return compativeis[0];
  if (!ativas.length && opcoes.podeFicarSem) return null;
  if (!compativeis.length) {
    recusar(
      opcoes.forma === "cash"
        ? "A empresa não tem caixa físico ativo para receber ou pagar em dinheiro. Cadastre um no painel, em Financeiro, Contas bancárias."
        : "A empresa não tem conta ativa que sirva para isto. Cadastre uma no painel, em Financeiro, Contas bancárias."
    );
  }
  return recusar(`Diga ${opcoes.papel}: ${listaDeNomes(compativeis)}.`);
}

export type Categoria = { id: string; name: string | null; type: string | null };

export async function categoriasFinanceiras(ctx: ContextoDoAssistente): Promise<Categoria[]> {
  const { data, error } = await ctx.admin
    .from("financial_categories")
    .select("id,name,type")
    .eq("company_id", empresaDe(ctx))
    .order("name", { ascending: true });
  if (error) falha("ler as categorias do financeiro", error);
  return (data ?? []) as Categoria[];
}

export async function escolherCategoria(ctx: ContextoDoAssistente, dito: unknown): Promise<Categoria | null> {
  if (dito === undefined || dito === null || !String(dito).trim()) return null;
  const todas = await categoriasFinanceiras(ctx);
  const escolha = escolherPorNome(todas, dito, (categoria) => [categoria.name]);
  const nomes = todas.map((categoria) => categoria.name).filter(Boolean).join(", ");
  if (escolha.tipo === "nenhum") {
    recusar(`Não achei a categoria "${String(dito)}". As categorias da empresa são: ${nomes || "nenhuma"}. Use uma delas (ou deixe sem categoria).`);
  }
  if (escolha.tipo === "varios") {
    recusar(`Mais de uma categoria combina com "${String(dito)}": ${escolha.itens.map((c) => c.name).join(", ")}. Diga qual.`);
  }
  return escolha.item;
}

export async function escolherFornecedor(ctx: ContextoDoAssistente, dito: unknown): Promise<{ id: string; name: string | null } | null> {
  if (dito === undefined || dito === null || !String(dito).trim()) return null;
  let consulta = ctx.admin.from("suppliers").select("id,name,legal_name").eq("company_id", empresaDe(ctx)).is("deleted_at", null).limit(30);
  const busca = limparBusca(dito);
  if (!/^[0-9a-f-]{36}$/i.test(String(dito).trim()) && busca) {
    consulta = consulta.or(`name.ilike.%${busca}%,legal_name.ilike.%${busca}%`);
  } else {
    consulta = consulta.eq("id", String(dito).trim());
  }
  const { data, error } = await consulta;
  if (error) falha("procurar o fornecedor", error);
  const achados = (data ?? []) as Array<{ id: string; name: string | null; legal_name: string | null }>;
  const escolha = escolherPorNome(achados, dito, (fornecedor) => [fornecedor.name, fornecedor.legal_name]);
  if (escolha.tipo === "nenhum") {
    recusar(`Não achei o fornecedor "${String(dito)}" no cadastro. Deixe sem fornecedor ou cadastre-o no painel, em Fornecedores.`);
  }
  if (escolha.tipo === "varios") {
    recusar(`Mais de um fornecedor combina com "${String(dito)}": ${escolha.itens.map((f) => f.name).join(", ")}. Diga qual.`);
  }
  return { id: escolha.item.id, name: escolha.item.name };
}
