/**
 * O que cada entidade do catálogo financeiro aceita — e o que recusa.
 *
 * Os dois casos que importam são recusas, e as duas vêm de defeitos MEDIDOS no
 * sistema de origem:
 *
 * 1. plano de contas sem direção. Lá, as 17 linhas eram todas `debito` —
 *    inclusive "Serviços" e "Comissão", que são coisas opostas. Um campo que
 *    existe e não distingue nada é pior que campo nenhum, porque dá a impressão
 *    de que alguém classificou.
 * 2. saldo inicial negativo PERMITIDO. Parece erro e não é: conta pode começar
 *    negativa (cheque especial, fatura aberta), e recusar obrigaria quem tem a
 *    mentir no cadastro.
 */
import { describe, expect, it } from "vitest";

import {
  contaSchema,
  ehEntidadeDoCatalogo,
  formaDePagamentoSchema,
  lerAlteracao,
  planoDeContaSchema,
  SCHEMA_POR_ENTIDADE,
  COLUNAS_POR_ENTIDADE,
  ENTIDADES_DO_CATALOGO,
} from "./catalogo";

describe("conta", () => {
  it("nasce em caixa e sem saldo quando não se diz nada — e SEM moeda", () => {
    const r = contaSchema.parse({ name: "Caixa" });
    expect(r).toMatchObject({ kind: "cash", opening_balance_cents: 0 });
    // A moeda saiu do schema no #3379: quem a resolve é o SERVIDOR
    // (`moedaDaOrganizacao`), a partir de `organizations.currency`. Um default
    // aqui era o defeito — a conta de organização em dólar nascia em real sem
    // ninguém ter escrito isso.
    expect(r).not.toHaveProperty("currency");
  });

  it("descarta a moeda que vier no corpo", () => {
    expect(contaSchema.parse({ name: "Caixa", currency: "EUR" })).not.toHaveProperty("currency");
  });

  it("aceita saldo inicial NEGATIVO — e isso é deliberado", () => {
    // Cheque especial e fatura aberta existem. Recusar obrigaria a mentir.
    expect(contaSchema.parse({ name: "Banco", opening_balance_cents: -150000 })
      .opening_balance_cents).toBe(-150000);
  });

  it("recusa tipo de conta inventado", () => {
    expect(contaSchema.safeParse({ name: "X", kind: "cripto" }).success).toBe(false);
  });

  it("recusa nome de uma letra", () => {
    expect(contaSchema.safeParse({ name: "C" }).success).toBe(false);
  });
});

describe("forma de pagamento", () => {
  it("pode nascer SEM conta — o catálogo se monta antes de o banco abrir", () => {
    // Recusar aqui impediria cadastrar "Pix" antes de decidir onde ele cai. A
    // checagem que importa é a da FINALIZAÇÃO da comanda, não a do cadastro.
    expect(formaDePagamentoSchema.safeParse({ name: "Pix" }).success).toBe(true);
    expect(formaDePagamentoSchema.safeParse({ name: "Pix", account_id: null }).success).toBe(true);
  });

  it("recusa conta que não é uuid", () => {
    expect(formaDePagamentoSchema.safeParse({ name: "Pix", account_id: "a-conta" }).success).toBe(
      false,
    );
  });
});

describe("plano de contas", () => {
  it("EXIGE direção — sem default", () => {
    // O sistema de origem tinha as 17 linhas como 'debito', inclusive
    // "Serviços" e "Comissão". Um default aqui reproduziria isso, porque quem
    // cadastra depressa aceita o que vem.
    expect(planoDeContaSchema.safeParse({ name: "Serviços" }).success).toBe(false);
    expect(planoDeContaSchema.safeParse({ name: "Serviços", direction: "in" }).success).toBe(true);
    expect(planoDeContaSchema.safeParse({ name: "Comissão", direction: "out" }).success).toBe(true);
  });

  it("recusa direção fora do vocabulário", () => {
    expect(planoDeContaSchema.safeParse({ name: "X", direction: "debito" }).success).toBe(false);
  });
});

describe("o mapa de entidades", () => {
  it("toda entidade tem schema, colunas e tabela", () => {
    for (const chave of Object.keys(ENTIDADES_DO_CATALOGO)) {
      const k = chave as keyof typeof ENTIDADES_DO_CATALOGO;
      expect(SCHEMA_POR_ENTIDADE[k], chave).toBeTruthy();
      expect(COLUNAS_POR_ENTIDADE[k], chave).toContain("id");
      expect(COLUNAS_POR_ENTIDADE[k], chave).toContain("is_active");
    }
  });

  it("a recorrência devolve a moeda — sem ela, a tela escreve o valor numa moeda fixa (#1531)", () => {
    // `recurring_entries.currency` existe desde que a tabela nasceu; o que
    // faltava era a coluna sair na resposta, e a tela caía em "BRL" fixo.
    expect(COLUNAS_POR_ENTIDADE.recorrencias.split(", ")).toContain("currency");
  });

  it("o guarda do path recusa nome de tabela cru", () => {
    // O path aceita `contas`, nunca `financial_accounts`: vocabulário de banco
    // na URL vaza o schema e amarra a API à tabela.
    expect(ehEntidadeDoCatalogo("contas")).toBe(true);
    expect(ehEntidadeDoCatalogo("financial_accounts")).toBe(false);
    expect(ehEntidadeDoCatalogo("../../etc")).toBe(false);
  });
});

describe("a edição (PATCH) — lerAlteracao", () => {
  it("toda entidade lê um corpo parcial sem lançar, e devolve SÓ a chave que veio", () => {
    // Duas falhas medidas na rota que já existia: `.partial()` sobre a regra de
    // comissão (que tem `.refine`) LANÇAVA no zod 4 — 500 sempre —, e na conta o
    // `.partial()` aplicava os defaults às chaves ausentes, então editar o nome
    // zerava o saldo inicial e voltava a moeda para BRL.
    for (const chave of Object.keys(ENTIDADES_DO_CATALOGO)) {
      const k = chave as keyof typeof ENTIDADES_DO_CATALOGO;
      const r = lerAlteracao(k, { name: "Novo nome" });
      expect(r, chave).toEqual({ ok: true, campos: { name: "Novo nome" } });
    }
  });

  it("regra de comissão: os dois alvos nulos são recusados; um só fica com o CHECK do banco", () => {
    expect(
      lerAlteracao("regras_de_comissao", { attendant_user_id: null, event_type_id: null }),
    ).toEqual({ ok: false, mensagem: "Escolha ao menos uma pessoa ou um serviço." });
    expect(lerAlteracao("regras_de_comissao", { attendant_user_id: null }).ok).toBe(true);
    expect(lerAlteracao("regras_de_comissao", { percent: 35 })).toEqual({
      ok: true,
      campos: { percent: 35 },
    });
  });

  it("a validação da criação vale na edição", () => {
    expect(lerAlteracao("contas", { name: "C" }).ok).toBe(false);
    expect(lerAlteracao("planos_de_conta", { direction: "debito" }).ok).toBe(false);
  });
});
