import { CATALOGO_ASIENTOS } from './catalogo-asientos';
import { MAPEO_DEFAULT } from '../seeds/plan-cuentas-paraguay.seed';
import { MapeoCuentasService } from './mapeo-cuentas.service';

/**
 * El catálogo es lo que el contador ve en pantalla antes de que exista un asiento
 * real: si un concepto está mal escrito, muestra "falta mapear" sobre una cuenta
 * que en realidad sí está mapeada, y si un asiento no cuadra, enseña algo que la
 * contabilidad nunca aceptaría.
 */
describe('Catálogo de asientos automáticos', () => {
  it('todos los conceptos existen en el mapeo del sistema', () => {
    const conocidos = new Set(Object.keys(MAPEO_DEFAULT));
    const desconocidos = CATALOGO_ASIENTOS.flatMap((s) =>
      s.lineas
        .filter((l) => l.concepto && !conocidos.has(l.concepto))
        .map((l) => `${s.clave} → ${l.concepto}`),
    );

    expect(desconocidos).toEqual([]);
  });

  it('cada situación cuadra: el debe del ejemplo es igual al haber', () => {
    const descuadradas = CATALOGO_ASIENTOS.filter((s) => {
      const debe = s.lineas.filter((l) => l.lado === 'DEBE').reduce((a, l) => a + l.monto, 0);
      const haber = s.lineas.filter((l) => l.lado === 'HABER').reduce((a, l) => a + l.monto, 0);
      return debe !== haber;
    }).map((s) => s.clave);

    expect(descuadradas).toEqual([]);
  });

  it('toda línea tiene cuenta: o un concepto del mapeo, o de dónde sale', () => {
    const sinOrigen = CATALOGO_ASIENTOS.flatMap((s) =>
      s.lineas.filter((l) => !l.concepto && !l.cuentaDinamica).map(() => s.clave),
    );

    expect(sinOrigen).toEqual([]);
  });

  it('cada situación tiene al menos una línea de cada lado y una clave única', () => {
    const claves = CATALOGO_ASIENTOS.map((s) => s.clave);
    expect(new Set(claves).size).toBe(claves.length);

    for (const s of CATALOGO_ASIENTOS) {
      expect(s.lineas.some((l) => l.lado === 'DEBE')).toBe(true);
      expect(s.lineas.some((l) => l.lado === 'HABER')).toBe(true);
    }
  });
});

describe('MapeoCuentasService.previsualizacionAsientos', () => {
  const crear = (
    mapeos: Array<{ concepto: string; codigo: string; descripcion: string }>,
    tes: { cuentas?: any[]; categorias?: any[]; mapeosCategoria?: any[]; plan?: any[]; mediosPago?: any[] } = {},
  ) => {
    const prisma: any = {
      cont_mapeo_cuentas: {
        findMany: jest.fn().mockResolvedValue(
          mapeos.map((m) => ({
            concepto: m.concepto,
            cuenta: { codigo: m.codigo, descripcion: m.descripcion },
          })),
        ),
      },
      tes_cuentas: { findMany: jest.fn().mockResolvedValue(tes.cuentas ?? []) },
      tes_categorias: { findMany: jest.fn().mockResolvedValue(tes.categorias ?? []) },
      tes_categoria_cuenta_contable: { findMany: jest.fn().mockResolvedValue(tes.mapeosCategoria ?? []) },
      cont_plan_cuentas: { findMany: jest.fn().mockResolvedValue(tes.plan ?? []) },
    };
    const service = Object.create(MapeoCuentasService.prototype) as any;
    service.prisma = prisma;
    service.mediosPago = {
      listar: jest.fn().mockResolvedValue({ medios: tes.mediosPago ?? [], total_configurados: 0 }),
    };
    return service;
  };

  it('pega la cuenta mapeada en cada línea y suma los totales', async () => {
    const service = crear([
      { concepto: 'CAJA_GENERAL', codigo: '1.01.01.01', descripcion: 'CAJA GENERAL' },
      { concepto: 'VENTAS_10', codigo: '4.01.01', descripcion: 'VENTAS GRAVADAS 10%' },
      { concepto: 'IVA_DEBITO_10', codigo: '2.01.03.01.02', descripcion: 'IVA A PAGAR' },
    ]);

    const { situaciones } = await service.previsualizacionAsientos('emp-1');
    const venta = situaciones.find((s: any) => s.clave === 'VENTA_CONTADO');
    const caja = venta.lineas.find((l: any) => l.concepto === 'CAJA_GENERAL');

    expect(caja.cuenta).toEqual({ codigo: '1.01.01.01', descripcion: 'CAJA GENERAL' });
    expect(caja.sin_mapear).toBe(false);
    expect(venta.total_debe).toBe(1_100_000);
    expect(venta.total_haber).toBe(1_100_000);
  });

  it('marca las líneas cuyo concepto no está mapeado todavía', async () => {
    const service = crear([{ concepto: 'CAJA_GENERAL', codigo: '1.01.01.01', descripcion: 'CAJA GENERAL' }]);

    const { situaciones } = await service.previsualizacionAsientos('emp-1');
    const venta = situaciones.find((s: any) => s.clave === 'VENTA_CONTADO');
    const ventas10 = venta.lineas.find((l: any) => l.concepto === 'VENTAS_10');

    expect(ventas10.sin_mapear).toBe(true);
    expect(ventas10.cuenta).toBeNull();
    expect(venta.conceptos_sin_mapear).toEqual(expect.arrayContaining(['VENTAS_10', 'IVA_DEBITO_10']));
  });

  it('una cuenta que no sale del mapeo no cuenta como falta', async () => {
    const service = crear([]);

    const { situaciones } = await service.previsualizacionAsientos('emp-1');
    const transferencia = situaciones.find((s: any) => s.clave === 'TRANSFERENCIA_TES');
    const destino = transferencia.lineas[0];

    expect(destino.sin_mapear).toBe(false);
    expect(destino.cuenta_dinamica).toMatch(/cuenta destino/i);
    expect(transferencia.conceptos_sin_mapear).toEqual([]);
  });

  it('trae las cuentas y categorías reales de tesorería, con y sin cuenta contable', async () => {
    const service = crear([], {
      cuentas: [
        { id: 'tc-1', nombre: 'DOBA S.A.', moneda: 'PYG', cuenta_contable_id: 'pc-1' },
        { id: 'tc-2', nombre: 'DOBA S.A. DOLARES', moneda: 'USD', cuenta_contable_id: null },
      ],
      categorias: [
        { id: 'cat-1', nombre: 'Depósito Bancario', tipo: 'INGRESO', cuenta_contable_id: 'pc-2' },
        { id: 'cat-2', nombre: 'Cheque Debitado', tipo: 'EGRESO', cuenta_contable_id: null },
      ],
      plan: [
        { id: 'pc-1', codigo: '1.01.01.06.01', descripcion: 'BANCO FAMILIAR SAECA' },
        { id: 'pc-2', codigo: '1.01.01.02', descripcion: 'RECAUDACIONES EFECTIVO' },
      ],
    });

    const { tesoreria } = await service.previsualizacionAsientos('emp-1');

    expect(tesoreria.cuentas).toEqual([
      { id: 'tc-1', nombre: 'DOBA S.A.', cuenta: { codigo: '1.01.01.06.01', descripcion: 'BANCO FAMILIAR SAECA' } },
      // La moneda se agrega al nombre para distinguir la cuenta en dólares.
      { id: 'tc-2', nombre: 'DOBA S.A. DOLARES (USD)', cuenta: null },
    ]);
    expect(tesoreria.categorias[0].cuenta).toEqual({ codigo: '1.01.01.02', descripcion: 'RECAUDACIONES EFECTIVO' });
    expect(tesoreria.categorias[1].cuenta).toBeNull();
  });

  it('el mapeo por empresa de una categoría gana sobre la cuenta propia', async () => {
    const service = crear([], {
      categorias: [{ id: 'cat-1', nombre: 'Otro Ingreso', tipo: 'INGRESO', cuenta_contable_id: 'pc-1' }],
      mapeosCategoria: [{ categoria_id: 'cat-1', cuenta_contable_id: 'pc-2' }],
      plan: [
        { id: 'pc-1', codigo: '1.01.01.06.01', descripcion: 'BANCO FAMILIAR SAECA' },
        { id: 'pc-2', codigo: '1.01.03.06.01', descripcion: 'ANTICIPOS A PROVEEDORES LOCALES' },
      ],
    });

    const { tesoreria } = await service.previsualizacionAsientos('emp-1');

    expect(tesoreria.categorias[0].cuenta.codigo).toBe('1.01.03.06.01');
  });

  it('las líneas de tesorería dicen de qué dato salen', async () => {
    const service = crear([]);

    const { situaciones } = await service.previsualizacionAsientos('emp-1');
    const ingreso = situaciones.find((s: any) => s.clave === 'MOVIMIENTO_TES_INGRESO');

    expect(ingreso.lineas.map((l: any) => l.fuente)).toEqual(['CUENTA_TESORERIA', 'CATEGORIA_TESORERIA']);
  });

  it('manda los medios de pago con la cuenta que la empresa eligió para cada uno', async () => {
    const service = crear([], {
      mediosPago: [
        {
          medio_pago_id: 'mp-1',
          codigo: 1,
          descripcion: 'Efectivo',
          cobro: { cuenta_id: 'pc-1', codigo: '1.01.01.02', descripcion: 'RECAUDACIONES EFECTIVO', usable: true },
          pago: null,
        },
      ],
    });

    const { medios_pago, situaciones } = await service.previsualizacionAsientos('emp-1');

    expect(medios_pago[0].cobro.codigo).toBe('1.01.01.02');
    // Y la línea del debe avisa que su cuenta sale del medio de pago.
    const venta = situaciones.find((s: any) => s.clave === 'VENTA_CONTADO');
    expect(venta.lineas[0].fuente).toBe('MEDIO_PAGO_COBRO');
  });

  it('dos categorías con la misma cuenta no comparten el objeto', async () => {
    // Compartir la referencia hacía que la API serializara la segunda como
    // "[Circular]" y la pantalla la mostrara sin cuenta.
    const service = crear([], {
      categorias: [
        { id: 'cat-1', nombre: 'Depósito Bancario', tipo: 'INGRESO', cuenta_contable_id: 'pc-1' },
        { id: 'cat-2', nombre: 'Otro Ingreso', tipo: 'INGRESO', cuenta_contable_id: 'pc-1' },
      ],
      plan: [{ id: 'pc-1', codigo: '1.01.01.02', descripcion: 'RECAUDACIONES EFECTIVO' }],
    });

    const { tesoreria } = await service.previsualizacionAsientos('emp-1');

    expect(tesoreria.categorias[0].cuenta).toEqual(tesoreria.categorias[1].cuenta);
    expect(tesoreria.categorias[0].cuenta).not.toBe(tesoreria.categorias[1].cuenta);
    // Lo que importa: sobrevive a la serialización sin "[Circular]".
    expect(JSON.parse(JSON.stringify(tesoreria)).categorias[1].cuenta.codigo).toBe('1.01.01.02');
  });
});
