import * as XLSX from 'xlsx';
import { CuentasPagarReportesService } from './cuentas-pagar-reportes.service';

jest.mock('src/utils/alcance-sucursal', () => ({ alcanceSucursalUsuario: jest.fn().mockResolvedValue(null) }));

const DIA = 86_400_000;
const hoyUtc = () => {
  const n = new Date();
  return Date.UTC(n.getFullYear(), n.getMonth(), n.getDate());
};
const enDias = (d: number) => new Date(hoyUtc() + d * DIA);

const persona = (razon_social: string) => ({ personas: { razon_social, ruc: '800', dv: '1' } });

function cuenta(o: Record<string, unknown>) {
  return {
    id: 'cp-x',
    proveedor_id: 'prov-1',
    compra_id: null,
    origen_tipo: 'gasto',
    origen_id: 'g-1',
    numero_cuota: 1,
    total_cuotas: 1,
    fecha_emision: enDias(-60),
    compra_cab: null,
    moneda: { codigo: 'PYG' },
    proveedores: persona('BALBINO'),
    ...o,
  };
}

function crear(cuentas: unknown[], pagos: unknown[] = []) {
  const prisma = {
    cuentas_pagar: { findMany: jest.fn().mockResolvedValue(cuentas) },
    gasto_cab: {
      findMany: jest.fn().mockResolvedValue([
        {
          id: 'g-1',
          numero_factura: '001-001-0000356',
          fecha_emision: enDias(-60),
          sucursal_id: 's-1',
          descripcion: 'Importado desde Libro de Compras SET',
          gasto_det: [{ tipo_gasto: { descripcion: 'HONORARIOS PROFESIONALES' } }],
        },
      ]),
    },
    pagos_proveedor: { findMany: jest.fn().mockResolvedValue(pagos) },
    empresas: { findUnique: jest.fn().mockResolvedValue({ razon_social: 'DOBA S.A.' }) },
  };
  return new CuentasPagarReportesService(prisma as never);
}

describe('CuentasPagarReportesService', () => {
  it('detalle: agrupa por comprobante, pone los pagos bajo su cuota y arma la antigüedad', async () => {
    const s = crear(
      [
        cuenta({ id: 'cp-1', numero_cuota: 1, total_cuotas: 2, fecha_vencimiento: enDias(-40), monto_original: 9_167_000, saldo_pendiente: 4_167_000 }),
        cuenta({ id: 'cp-2', numero_cuota: 2, total_cuotas: 2, fecha_vencimiento: enDias(10), monto_original: 9_167_000, saldo_pendiente: 9_167_000 }),
      ],
      [{ cuenta_pagar_id: 'cp-1', fecha_pago: enDias(-20), numero_recibo: '001-001-0000027', monto: 5_000_000, medio_pago: { descripcion: 'TRANSFERENCIA' } }],
    );
    const d = await s.detalle('emp', undefined);

    expect(d.proveedores).toHaveLength(1);
    const p = d.proveedores[0];
    expect(p.ruc).toBe('800-1');
    expect(p.documentos).toHaveLength(1);
    const doc = p.documentos[0];
    // El concepto sale del tipo de gasto, no de la descripción genérica.
    expect(doc.concepto).toBe('HONORARIOS PROFESIONALES');
    expect(doc.cuotas.map((q) => [q.nro, q.de, q.estado, q.pagado])).toEqual([
      [1, 2, 'Vencida', 5_000_000],
      [2, 2, 'Por vencer', 0],
    ]);
    expect(doc.cuotas[0].dias).toBe(40);
    expect(doc.cuotas[1].dias).toBe(-10);
    expect(doc.cuotas[0].pagos).toEqual([expect.objectContaining({ numero: '001-001-0000027', medio: 'TRANSFERENCIA', monto: 5_000_000 })]);
    expect(p.saldo).toBe(13_334_000);
    expect(p.vencido).toBe(4_167_000);
    expect(p.antiguedad).toEqual({ al_dia: 9_167_000, d1_30: 0, d31_60: 4_167_000, d61_90: 0, mas_90: 0 });
  });

  it('detalle: no mezcla monedas del mismo proveedor y ordena por deuda', async () => {
    const s = crear([
      cuenta({ id: 'a', proveedor_id: 'p-chico', proveedores: persona('CHICO'), fecha_vencimiento: enDias(-1), monto_original: 100, saldo_pendiente: 100 }),
      cuenta({ id: 'b', proveedor_id: 'p-grande', proveedores: persona('GRANDE'), fecha_vencimiento: enDias(-1), monto_original: 900, saldo_pendiente: 900 }),
      cuenta({ id: 'c', proveedor_id: 'p-grande', proveedores: persona('GRANDE'), moneda: { codigo: 'USD' }, fecha_vencimiento: enDias(-1), monto_original: 50, saldo_pendiente: 50 }),
    ]);
    const d = await s.detalle('emp', undefined);

    expect(d.proveedores.map((p) => [p.razon_social, p.moneda, p.saldo])).toEqual([
      ['GRANDE', 'PYG', 900],
      ['CHICO', 'PYG', 100],
      ['GRANDE', 'USD', 50],
    ]);
    expect(d.totales).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ moneda: 'PYG', saldo: 1000 }),
        expect.objectContaining({ moneda: 'USD', saldo: 50 }),
      ]),
    );
  });

  it('plan de pagos: lo vencido va a «Vencido», lo demás al mes en que vence o a «Después»', async () => {
    const n = new Date(hoyUtc());
    const finDeMes = new Date(Date.UTC(n.getUTCFullYear(), n.getUTCMonth() + 1, 0));
    const s = crear([
      cuenta({ id: 'v', fecha_vencimiento: enDias(-5), monto_original: 10, saldo_pendiente: 10 }),
      cuenta({ id: 'm', fecha_vencimiento: finDeMes, monto_original: 20, saldo_pendiente: 20 }),
      cuenta({ id: 'lejos', fecha_vencimiento: enDias(400), monto_original: 30, saldo_pendiente: 30 }),
    ]);
    const plan = await s.planPagos('emp', undefined, { meses: 2 });

    expect(plan.columnas).toHaveLength(4); // Vencido, mes actual, mes siguiente, Después
    const [vencido, mesActual, , despues] = plan.columnas.map((c) => c.clave);
    expect(plan.totales[0].por_columna).toEqual(expect.objectContaining({ [vencido]: 10, [mesActual]: 20, [despues]: 30 }));
  });

  it('el Excel del detalle trae la hoja de antigüedad y los montos con separador de miles', async () => {
    const s = crear([cuenta({ id: 'cp-1', fecha_vencimiento: enDias(-40), monto_original: 9_167_000, saldo_pendiente: 9_167_000 })]);
    const wb = XLSX.read(await s.detalleXlsx('emp', undefined));

    expect(wb.SheetNames).toEqual(['Detalle', 'Antigüedad']);
    const filas = XLSX.utils.sheet_to_json<unknown[]>(wb.Sheets.Detalle, { header: 1, defval: '' });
    const cuota = filas.find((r) => r[1] === '001-001-0000356');
    expect(cuota[3]).toBe('HONORARIOS PROFESIONALES');
    expect(cuota[9]).toBe(9_167_000);
  });
});
