import { CANAIS, DIMENSOES, ROTULO_DA_DIMENSAO } from "@/lib/paineis/agregar";
import { catalogoPublico, criarFonte, IDS_DAS_METRICAS, metricaPorId, podeVerMetrica, ROTULO_DO_TIPO, TIPOS_DE_WIDGET } from "@/lib/paineis/metricas";
import { hojeNoBrasil, PERIODOS, ROTULO_DO_PERIODO } from "@/lib/paineis/periodos";
import {
  alterarPainel,
  buscarPainel,
  calcularWidget,
  calcularWidgets,
  criarPainel,
  ErroDoPainel,
  excluirPainel,
  listarPaineis,
  type PainelGravado,
  type ResultadoDoWidget,
} from "@/lib/paineis/servidor";
import { conferirNome, conferirWidget, conferirWidgets, MAXIMO_DE_WIDGETS, type Widget } from "@/lib/paineis/widgets";

import type { ContextoDoAssistente } from "../contexto";
import type { Campos } from "../esquema";
import { AVISO_DE_DADOS, exigir, pode, recusar } from "./comum";
import type { Argumentos, Ferramenta, FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/**
 * Painéis: telas de números que a empresa monta, e perguntas soltas de
 * número ("quanto faturei este mês por vendedor").
 *
 * O assistente nunca escreve consulta: escolhe uma métrica do catálogo
 * fechado (lib/paineis/metricas.ts), um período e um agrupamento.
 *
 * A tabela `paineis` é lida e gravada com a sessão da pessoa (as travas do
 * banco valem). Os NÚMEROS são lidos como na tela de painéis e no relatório
 * (app/api/reports): pela chave de serviço, com toda consulta presa às contas
 * da empresa (`tenantIds`) e a permissão conferida antes — "reports.read"
 * para qualquer métrica, e a do financeiro, uma a uma, para as do financeiro.
 * Pela sessão, as travas de contas a pagar e do caixa dependem de um dado do
 * login (company_id no token) que a sessão do assistente não carrega, e o
 * assistente responderia R$ 0,00 onde a tela mostra o valor certo (achado no
 * teste com a Aurora, 14/10/2026). O mesmo motivo levou o financeiro do
 * assistente a ler essas tabelas pela chave de serviço.
 */

function fonteDo(ctx: ContextoDoAssistente) {
  return criarFonte({
    db: ctx.admin,
    tenantIds: ctx.tenantIds,
    companyId: ctx.empresa.companyId,
    donoId: ctx.donoDosDados,
    hoje: hojeNoBrasil(),
    pode: (permissao) => pode(ctx, permissao as Parameters<typeof pode>[1]),
  });
}

function linkDoPainel(ctx: ContextoDoAssistente, id: string) {
  return `${ctx.origemDoAdmin.replace(/\/$/, "")}/dashboard/paineis/${id}`;
}

/** Erro do painel vira erro que o assistente lê. */
async function semErroDoPainel<T>(acao: () => Promise<T>): Promise<T> {
  try {
    return await acao();
  } catch (erro) {
    if (erro instanceof ErroDoPainel) recusar(erro.message);
    throw erro;
  }
}

/* O resultado de um quadro, enxuto para o assistente ler. */
function resultadoLegivel(widget: Widget, r: ResultadoDoWidget | undefined) {
  if (!r) return { quadro: widget.titulo, erro: "Sem resultado." };
  return {
    quadro_id: widget.id,
    quadro: widget.titulo,
    metrica: widget.metrica,
    tipo: widget.tipo,
    formato: r.formato,
    periodo: r.periodo,
    valor: r.valor,
    ...(r.anterior ? { periodo_anterior: r.anterior } : {}),
    ...(r.grupos ? { agrupado: r.rotulo_do_grupo, grupos: r.grupos.map((g) => ({ rotulo: g.rotulo, valor: g.valor, ...(g.extra ?? {}) })) } : {}),
    ...(r.colunas.length && r.grupos ? { colunas_extras: r.colunas.map((c) => c.id) } : {}),
    ...(r.aviso ? { aviso: r.aviso } : {}),
    ...(r.erro ? { erro: r.erro } : {}),
  };
}

function exigirVerPaineis(ctx: ContextoDoAssistente) {
  exigir(ctx, "reports.read", "ver painéis e relatórios");
}

function exigirEditarPaineis(ctx: ContextoDoAssistente) {
  exigir(ctx, "company.settings", "criar ou alterar painéis");
}

/* ---------------------------------------------------------- campos comuns */

const CAMPOS_DO_QUADRO: Campos = {
  titulo: { tipo: "texto", descricao: "Título do quadro. Sem isto, usa o nome da métrica.", max: 80 },
  metrica: { tipo: "opcao", opcoes: IDS_DAS_METRICAS, descricao: "Id da métrica (veja listar_metricas).", obrigatorio: true },
  tipo: { tipo: "opcao", opcoes: TIPOS_DE_WIDGET, descricao: "Como mostrar: numero (com comparação ao período anterior), barras, linha (ao longo do tempo), pizza ou tabela. Sem isto, usa o sugerido da métrica." },
  periodo: { tipo: "opcao", opcoes: PERIODOS, descricao: "Período. Padrão: este_mes. Use personalizado com de e ate." },
  de: { tipo: "data", descricao: "Início, só com periodo personalizado." },
  ate: { tipo: "data", descricao: "Fim, só com periodo personalizado." },
  agrupar_por: { tipo: "opcao", opcoes: DIMENSOES, descricao: "Como dividir o número (cada métrica aceita alguns; veja listar_metricas)." },
  limite: { tipo: "inteiro", min: 1, max: 50, descricao: "Quantos grupos mostrar (o resto vira Outros). Padrão: 10." },
  canal: { tipo: "opcao", opcoes: Object.keys(CANAIS), descricao: "Filtrar por um canal de venda (só nas métricas que aceitam)." },
  vendedor_id: { tipo: "id", descricao: "Filtrar por um vendedor (id da pessoa; só nas métricas que aceitam)." },
};

/** O quadro como o assistente manda vira o formato guardado. */
function quadroDe(args: Argumentos) {
  const filtros: Record<string, unknown> = {};
  if (args.canal) filtros.canal = args.canal;
  if (args.vendedor_id) filtros.vendedor = args.vendedor_id;
  return {
    titulo: args.titulo,
    metrica: args.metrica,
    tipo: args.tipo,
    periodo: args.periodo,
    de: args.de,
    ate: args.ate,
    agrupar_por: args.agrupar_por,
    limite: args.limite,
    filtros: Object.keys(filtros).length ? filtros : undefined,
  };
}

function conferirQuadros(lista: unknown): Widget[] {
  const quadros = Array.isArray(lista) ? (lista as Argumentos[]).map(quadroDe) : [];
  const conferido = conferirWidgets(quadros);
  if (!conferido.ok) recusar(conferido.erro);
  return conferido.valor;
}

/** Avisa, antes de gravar, dos quadros que esta pessoa não vai conseguir ver (financeiro). */
function avisosDePermissao(ctx: ContextoDoAssistente, quadros: Widget[]) {
  const avisos: string[] = [];
  for (const quadro of quadros) {
    const metrica = metricaPorId(quadro.metrica);
    if (metrica && !podeVerMetrica(metrica, (p) => pode(ctx, p as Parameters<typeof pode>[1]))) {
      avisos.push(`O quadro "${quadro.titulo}" é do financeiro: quem não tem permissão do financeiro vai vê-lo sem números.`);
    }
  }
  return avisos;
}

function resumoDoPainel(ctx: ContextoDoAssistente, painel: PainelGravado) {
  return {
    painel_id: painel.id,
    nome: painel.nome,
    descricao: painel.descricao,
    quadros: painel.widgets.map((w) => ({
      quadro_id: w.id,
      titulo: w.titulo,
      metrica: w.metrica,
      tipo: w.tipo,
      periodo: w.periodo,
      ...(w.periodo === "personalizado" ? { de: w.de, ate: w.ate } : {}),
      ...(w.agrupar_por ? { agrupar_por: w.agrupar_por } : {}),
      ...(w.filtros ? { filtros: w.filtros } : {}),
    })),
    link: linkDoPainel(ctx, painel.id),
  };
}

/* ------------------------------------------------------------- leituras */

const listarMetricas: FerramentaDeLeitura = {
  nome: "listar_metricas",
  titulo: "Ver as métricas dos painéis",
  descricao:
    "Lista as métricas que dá para consultar e pôr num painel (faturamento, pedidos, ticket médio, orçamentos, conversão, vendas por vendedor e por canal, produtos mais vendidos, clientes novos, pedidos por etapa e atrasados, contas a receber e a pagar, fluxo de caixa), com os agrupamentos, filtros e tipos de gráfico que cada uma aceita, e os períodos. Chame antes de consultar_metrica ou criar_painel.",
  campos: {},
  tipo: "leitura",
  permissoes: ["reports.read"],
  async executar(ctx) {
    exigirVerPaineis(ctx);
    return {
      dados: {
        metricas: catalogoPublico(),
        periodos: PERIODOS.map((id) => ({ id, rotulo: ROTULO_DO_PERIODO[id] })),
        tipos_de_quadro: TIPOS_DE_WIDGET.map((id) => ({ id, rotulo: ROTULO_DO_TIPO[id] })),
        agrupamentos: DIMENSOES.map((id) => ({ id, rotulo: ROTULO_DA_DIMENSAO[id] })),
        canais: Object.entries(CANAIS).map(([id, rotulo]) => ({ id, rotulo })),
        observacoes: [
          "Venda = pedido criado no período, sem cancelados. Faturamento é o valor vendido, não o recebido (para o recebido, use fluxo_de_caixa).",
          "O sistema não separa WhatsApp de balcão: o que a equipe lança no painel aparece como 'Painel (equipe)'. 'Balcão (PDV)' é a venda rápida do PDV.",
          "Métricas marcadas como do financeiro só mostram números a quem tem permissão do financeiro.",
        ],
      },
      resumo: "Consultou as métricas dos painéis",
    };
  },
};

const consultarMetrica: FerramentaDeLeitura = {
  nome: "consultar_metrica",
  titulo: "Consultar um número",
  descricao:
    "Responde uma pergunta de número na hora, sem criar painel. Ex.: 'quanto faturei este mês por vendedor' = metrica faturamento, periodo este_mes, agrupar_por vendedor. Sem agrupar_por, devolve o total e a comparação com o período anterior.",
  campos: {
    metrica: CAMPOS_DO_QUADRO.metrica,
    periodo: CAMPOS_DO_QUADRO.periodo,
    de: CAMPOS_DO_QUADRO.de,
    ate: CAMPOS_DO_QUADRO.ate,
    agrupar_por: CAMPOS_DO_QUADRO.agrupar_por,
    limite: CAMPOS_DO_QUADRO.limite,
    canal: CAMPOS_DO_QUADRO.canal,
    vendedor_id: CAMPOS_DO_QUADRO.vendedor_id,
  },
  tipo: "leitura",
  permissoes: ["reports.read"],
  async executar(ctx, args) {
    exigirVerPaineis(ctx);
    const metrica = metricaPorId(args.metrica);
    if (!metrica) recusar("Métrica não existe. Veja listar_metricas.");
    /* Métricas que já nascem divididas (vendas por vendedor, produtos mais
       vendidos) respondem divididas mesmo sem agrupar_por. */
    const agruparPor = args.agrupar_por ?? (metrica.tipoSugerido !== "numero" && metrica.agruparPadrao !== "tempo" ? metrica.agruparPadrao : null);
    const tipo = !agruparPor ? "numero" : agruparPor === "tempo" ? (metrica.tipos.includes("linha") ? "linha" : "tabela") : "tabela";
    const conferido = conferirWidget({ ...quadroDe(args), agrupar_por: agruparPor, tipo: metrica.tipos.includes(tipo) ? tipo : metrica.tipoSugerido });
    if (!conferido.ok) recusar(conferido.erro.replace(/^Quadro 1: /, ""));
    const quadro = conferido.valor;
    const resultado = await calcularWidget(fonteDo(ctx), quadro);
    if (resultado.erro) recusar(resultado.erro);
    return {
      dados: { _observacao: AVISO_DE_DADOS, ...resultadoLegivel(quadro, resultado) },
      resumo: `Consultou ${metrica.rotulo.toLowerCase()}${quadro.agrupar_por && args.agrupar_por ? ` ${ROTULO_DA_DIMENSAO[quadro.agrupar_por].toLowerCase()}` : ""}`,
    };
  },
};

const listarPaineisFerramenta: FerramentaDeLeitura = {
  nome: "listar_paineis",
  titulo: "Ver os painéis",
  descricao: "Lista os painéis que a empresa já montou, com os quadros de cada um e o link para abrir no sistema.",
  campos: {},
  tipo: "leitura",
  permissoes: ["reports.read"],
  async executar(ctx) {
    exigirVerPaineis(ctx);
    const paineis = await semErroDoPainel(() => listarPaineis(ctx.supabase, ctx.donoDosDados));
    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        paineis: paineis.map((p) => ({
          painel_id: p.id,
          nome: p.nome,
          descricao: p.descricao,
          quadros: p.widgets.map((w) => w.titulo),
          link: linkDoPainel(ctx, p.id),
        })),
        ...(paineis.length ? {} : { dica: "Nenhum painel ainda. Crie um com criar_painel." }),
      },
      resumo: `Listou os painéis (${paineis.length})`,
    };
  },
};

const verPainel: FerramentaDeLeitura = {
  nome: "ver_painel",
  titulo: "Ver um painel com os números",
  descricao: "Abre um painel e devolve os números de cada quadro já calculados, com o link para a pessoa abrir no sistema.",
  campos: { painel_id: { tipo: "id", descricao: "Id do painel (de listar_paineis).", obrigatorio: true } },
  tipo: "leitura",
  permissoes: ["reports.read"],
  async executar(ctx, args) {
    exigirVerPaineis(ctx);
    const painel = await semErroDoPainel(() => buscarPainel(ctx.supabase, ctx.donoDosDados, String(args.painel_id)));
    const dados = await calcularWidgets(fonteDo(ctx), painel.widgets);
    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        painel_id: painel.id,
        nome: painel.nome,
        descricao: painel.descricao,
        link: linkDoPainel(ctx, painel.id),
        quadros: painel.widgets.map((w) => resultadoLegivel(w, dados[w.id])),
      },
      resumo: `Abriu o painel ${painel.nome}`,
    };
  },
};

/* --------------------------------------------------------------- escritas */

type CargaDoNovo = { nome: string; descricao: string | null; widgets: Widget[] };

const criarPainelFerramenta: FerramentaDeEscrita<CargaDoNovo> = {
  nome: "criar_painel",
  titulo: "Criar um painel",
  descricao:
    "Cria um painel de números para a empresa, com seus quadros (cada quadro = uma métrica do listar_metricas, um tipo de gráfico, um período e um agrupamento). Ex.: 'faturamento por vendedor neste mês, ticket médio e os 10 produtos que mais vendem' = três quadros. Devolve o link para abrir no sistema.",
  campos: {
    nome: { tipo: "texto", descricao: "Nome do painel.", obrigatorio: true, max: 80 },
    descricao: { tipo: "texto", descricao: "Para que serve o painel.", max: 400 },
    quadros: { tipo: "lista", itens: CAMPOS_DO_QUADRO, minItens: 1, maxItens: MAXIMO_DE_WIDGETS, descricao: "Os quadros, na ordem em que aparecem.", obrigatorio: true },
  },
  tipo: "escrita",
  permissoes: ["company.settings"],
  async preparar(ctx, args) {
    exigirEditarPaineis(ctx);
    const nome = conferirNome(args.nome);
    if (!nome.ok) recusar(nome.erro);
    const widgets = conferirQuadros(args.quadros);
    const descricao = typeof args.descricao === "string" ? args.descricao : null;
    return {
      previa: {
        nome: nome.valor,
        descricao,
        quadros: widgets.map((w) => ({ titulo: w.titulo, metrica: w.metrica, tipo: w.tipo, periodo: w.periodo, agrupar_por: w.agrupar_por })),
      },
      resumo: `Criar o painel ${nome.valor} com ${widgets.length} ${widgets.length === 1 ? "quadro" : "quadros"}`,
      avisos: avisosDePermissao(ctx, widgets),
      carga: { nome: nome.valor, descricao, widgets },
    };
  },
  async gravar(ctx, carga) {
    exigirEditarPaineis(ctx);
    const painel = await semErroDoPainel(() => criarPainel(ctx.supabase, ctx.donoDosDados, ctx.user.id, carga));
    return {
      dados: { criado: true, ...resumoDoPainel(ctx, painel), dica: "Para ver os números, chame ver_painel ou abra o link." },
      resumo: `Criou o painel ${painel.nome}`,
    };
  },
};

type CargaDaMudanca = { id: string; nome: string; mudancas: { nome?: string; descricao?: string | null; widgets?: Widget[] } };

const alterarPainelFerramenta: FerramentaDeEscrita<CargaDaMudanca> = {
  nome: "alterar_painel",
  titulo: "Alterar um painel",
  descricao:
    "Muda um painel: nome, descrição e quadros. Para os quadros, use UM destes: `quadros` troca a lista inteira; `adicionar_quadros` põe no fim; `remover_quadros` tira pelos quadro_id (de ver_painel ou listar_paineis). Para reordenar, mande `quadros` na ordem nova.",
  campos: {
    painel_id: { tipo: "id", descricao: "Id do painel.", obrigatorio: true },
    nome: { tipo: "texto", descricao: "Nome novo.", max: 80 },
    descricao: { tipo: "texto", descricao: "Descrição nova.", max: 400 },
    quadros: { tipo: "lista", itens: { quadro_id: { tipo: "texto", descricao: "Id de um quadro que já existe, para mantê-lo com o mesmo id.", max: 40 }, ...CAMPOS_DO_QUADRO }, maxItens: MAXIMO_DE_WIDGETS, descricao: "A lista inteira de quadros, na ordem nova." },
    adicionar_quadros: { tipo: "lista", itens: CAMPOS_DO_QUADRO, maxItens: MAXIMO_DE_WIDGETS, descricao: "Quadros para pôr no fim." },
    remover_quadros: { tipo: "textos", max: 40, maxItens: MAXIMO_DE_WIDGETS, descricao: "Os quadro_id a tirar." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["company.settings"],
  async preparar(ctx, args) {
    exigirEditarPaineis(ctx);
    const painel = await semErroDoPainel(() => buscarPainel(ctx.supabase, ctx.donoDosDados, String(args.painel_id)));
    const formasDeQuadro = ["quadros", "adicionar_quadros", "remover_quadros"].filter((campo) => args[campo] !== undefined);
    if (formasDeQuadro.length > 1) recusar("Use só um destes por vez: quadros, adicionar_quadros ou remover_quadros.");

    const mudancas: CargaDaMudanca["mudancas"] = {};
    if (args.nome !== undefined) {
      const nome = conferirNome(args.nome);
      if (!nome.ok) recusar(nome.erro);
      mudancas.nome = nome.valor;
    }
    if (args.descricao !== undefined) mudancas.descricao = String(args.descricao);

    if (args.quadros !== undefined) {
      const lista = (args.quadros as Argumentos[]).map((q) => ({ ...quadroDe(q), id: q.quadro_id }));
      const conferido = conferirWidgets(lista);
      if (!conferido.ok) recusar(conferido.erro);
      mudancas.widgets = conferido.valor;
    } else if (args.adicionar_quadros !== undefined) {
      const novos = conferirQuadros(args.adicionar_quadros);
      const conferido = conferirWidgets([...painel.widgets, ...novos]);
      if (!conferido.ok) recusar(conferido.erro);
      mudancas.widgets = conferido.valor;
    } else if (args.remover_quadros !== undefined) {
      const tirar = new Set(args.remover_quadros as string[]);
      const naoAchados = [...tirar].filter((id) => !painel.widgets.some((w) => w.id === id));
      if (naoAchados.length) recusar(`Quadro não encontrado neste painel: ${naoAchados.join(", ")}. Veja os quadro_id com ver_painel.`);
      mudancas.widgets = painel.widgets.filter((w) => !tirar.has(w.id));
    }
    if (!Object.keys(mudancas).length) recusar("Nada para mudar: mande nome, descricao ou os quadros.");

    return {
      previa: {
        painel: painel.nome,
        ...(mudancas.nome ? { nome_novo: mudancas.nome } : {}),
        ...(mudancas.descricao !== undefined ? { descricao_nova: mudancas.descricao } : {}),
        ...(mudancas.widgets ? { quadros_antes: painel.widgets.map((w) => w.titulo), quadros_depois: mudancas.widgets.map((w) => w.titulo) } : {}),
      },
      resumo: `Alterar o painel ${painel.nome}`,
      avisos: mudancas.widgets ? avisosDePermissao(ctx, mudancas.widgets) : [],
      carga: { id: painel.id, nome: painel.nome, mudancas },
    };
  },
  async gravar(ctx, carga) {
    exigirEditarPaineis(ctx);
    const painel = await semErroDoPainel(() => alterarPainel(ctx.supabase, ctx.donoDosDados, carga.id, carga.mudancas));
    return { dados: { alterado: true, ...resumoDoPainel(ctx, painel) }, resumo: `Alterou o painel ${painel.nome}` };
  },
};

const excluirPainelFerramenta: FerramentaDeEscrita<{ id: string; nome: string }> = {
  nome: "excluir_painel",
  titulo: "Excluir um painel",
  descricao: "Exclui um painel (só a tela de números; nenhum pedido, orçamento ou lançamento é apagado). Sempre passa pela prévia com código.",
  campos: { painel_id: { tipo: "id", descricao: "Id do painel.", obrigatorio: true } },
  tipo: "escrita",
  sensivel: true,
  sobrescreve: true,
  permissoes: ["company.settings"],
  async preparar(ctx, args) {
    exigirEditarPaineis(ctx);
    const painel = await semErroDoPainel(() => buscarPainel(ctx.supabase, ctx.donoDosDados, String(args.painel_id)));
    return {
      previa: { excluir: painel.nome, quadros: painel.widgets.map((w) => w.titulo) },
      resumo: `Excluir o painel ${painel.nome}`,
      avisos: ["O painel some para toda a equipe. Os dados da empresa (pedidos, orçamentos, financeiro) não são apagados."],
      carga: { id: painel.id, nome: painel.nome },
    };
  },
  async gravar(ctx, carga) {
    exigirEditarPaineis(ctx);
    await semErroDoPainel(() => excluirPainel(ctx.supabase, ctx.donoDosDados, carga.id));
    return { dados: { excluido: true, painel_id: carga.id, nome: carga.nome }, resumo: `Excluiu o painel ${carga.nome}` };
  },
};

export const FERRAMENTAS_DE_PAINEIS: Ferramenta[] = [
  listarMetricas,
  consultarMetrica,
  listarPaineisFerramenta,
  verPainel,
  criarPainelFerramenta as Ferramenta,
  alterarPainelFerramenta as Ferramenta,
  excluirPainelFerramenta as Ferramenta,
];
