import { AsientosService } from './asientos.service';

/**
 * Corregir un asiento confirmado lo modifica en el lugar. Antes lo revertía
 * (asiento espejo) y abría uno nuevo: por un error de carga quedaban tres
 * asientos y el libro diario se volvía difícil de leer.
 */
describe('Corregir asiento confirmado', () => {
  const original = {
    id: 'as-1',
    numero: 145,
    estado: 'CONFIRMADO',
    periodo_id: 'per-oct',
    moneda_origen: 'PYG',
    fecha: new Date('2026-10-08'),
    glosa: 'Recibo multi N° 001-001-0000545',
    lineas: [
      { cuenta_id: 'caja', centro_costo_id: null, descripcion: null, debe_moneda: 100_000, haber_moneda: 0 },
      { cuenta_id: 'clientes', centro_costo_id: null, descripcion: null, debe_moneda: 0, haber_moneda: 100_000 },
    ],
  };
  const corregidas = [
    { cuenta_id: 'banco', debe_moneda: 100_000, haber_moneda: 0 },
    { cuenta_id: 'clientes', debe_moneda: 0, haber_moneda: 100_000 },
  ];

  const foto = (lineas: [string, number, number][]) => ({
    id: 'as-1',
    numero: 145,
    fecha: new Date('2026-10-08'),
    glosa: 'Recibo multi N° 001-001-0000545',
    total_debe_pyg: 100_000,
    periodo: { mes: 10, anio: 2026 },
    documento: null,
    lineas: lineas.map(([c, d, h]) => ({ debe_pyg: d, haber_pyg: h, descripcion: null, cuenta: { codigo: c, descripcion: c.toUpperCase() } })),
  });

  const armar = (asiento: any = original) => {
    const tx = {
      cont_asientos_det: { deleteMany: jest.fn(), createMany: jest.fn() },
      cont_asientos: { update: jest.fn(), create: jest.fn() },
    };
    const service = Object.create(AsientosService.prototype) as any;
    service.prisma = {
      cont_asientos: {
        findFirst: jest.fn().mockResolvedValue(asiento),
        // Foto para la auditoría: la primera llamada es el "antes" y la segunda el "después".
        findUnique: jest
          .fn()
          .mockResolvedValueOnce(foto(asiento.lineas.map((l: any) => [l.cuenta_id, l.debe_moneda, l.haber_moneda])))
          .mockResolvedValueOnce(foto(corregidas.map((l) => [l.cuenta_id, l.debe_moneda, l.haber_moneda]))),
      },
      cont_periodos: {
        findFirst: jest.fn().mockResolvedValue({ fecha_inicio: new Date('2026-10-01'), fecha_fin: new Date('2026-10-31') }),
      },
      cont_plan_cuentas: { findFirst: jest.fn().mockResolvedValue({ acepta_movimientos: true, acepta_cc: false }) },
      $transaction: jest.fn().mockImplementation((fn: any) => fn(tx)),
    };
    service.periodosService = { validarPeriodoAbierto: jest.fn().mockResolvedValue(undefined) };
    service.tipoCambioService = { convertirAPYG: jest.fn().mockImplementation((_e: string, m: number) => m) };
    service.audit = { log: jest.fn() };
    service.revertir = jest.fn();
    service.getById = jest.fn().mockResolvedValue({ id: asiento.id });
    return { service, tx };
  };

  it('reemplaza las líneas del mismo asiento y no genera asiento espejo', async () => {
    const { service, tx } = armar();

    await service.corregir('as-1', 'emp-1', 'usr-1', { lineas: corregidas });

    expect(service.revertir).not.toHaveBeenCalled();
    expect(tx.cont_asientos.create).not.toHaveBeenCalled();
    expect(tx.cont_asientos_det.deleteMany).toHaveBeenCalledWith({ where: { asiento_id: 'as-1' } });
    const nuevas = tx.cont_asientos_det.createMany.mock.calls[0][0].data;
    expect(nuevas.map((l: any) => l.cuenta_id)).toEqual(['banco', 'clientes']);
    // Sigue confirmado: no se toca el estado.
    expect(tx.cont_asientos.update.mock.calls[0][0].data.estado).toBeUndefined();
  });

  it('deja en auditoría cómo estaba antes', async () => {
    const { service } = armar();

    await service.corregir('as-1', 'emp-1', 'usr-1', { lineas: corregidas });

    const entrada = service.audit.log.mock.calls[0][0];
    expect(entrada.action).toBe('CORRECT');
    expect(entrada.old_value.asiento.lineas.map((l: any) => l.cuenta)).toEqual(['caja CAJA', 'clientes CLIENTES']);
    expect(entrada.new_value.asiento.lineas.map((l: any) => l.cuenta)).toEqual(['banco BANCO', 'clientes CLIENTES']);
    expect(entrada.descripcion).toContain('caja CAJA: 100.000 → 0');
    expect(entrada.descripcion).toContain('banco BANCO: 0 → 100.000');
  });

  it('rechaza una corrección descuadrada sin tocar nada', async () => {
    const { service, tx } = armar();

    await expect(
      service.corregir('as-1', 'emp-1', 'usr-1', { lineas: [corregidas[0], { ...corregidas[1], haber_moneda: 90_000 }] }),
    ).rejects.toThrow(/no está balanceado/);
    expect(tx.cont_asientos_det.deleteMany).not.toHaveBeenCalled();
  });

  it('rechaza una fecha fuera del período del asiento', async () => {
    const { service } = armar();

    await expect(
      service.corregir('as-1', 'emp-1', 'usr-1', { fecha: '2026-11-02', lineas: corregidas }),
    ).rejects.toThrow(/fuera del período/);
  });

  it('con el período cerrado no se corrige (corresponde Revertir)', async () => {
    const { service } = armar();
    service.periodosService.validarPeriodoAbierto.mockRejectedValue(new Error('El período está cerrado'));

    await expect(service.corregir('as-1', 'emp-1', 'usr-1', { lineas: corregidas })).rejects.toThrow(/cerrado/);
  });

  it('solo corrige asientos confirmados', async () => {
    const { service } = armar({ ...original, estado: 'REVERTIDO' });

    await expect(service.corregir('as-1', 'emp-1', 'usr-1', { lineas: corregidas })).rejects.toThrow(/CONFIRMADOS/);
  });
});
