import { BadRequestException } from '@nestjs/common';
import { TesChequeService } from './tes-cheques.service';

/**
 * Dos situaciones distintas, con tratamiento distinto:
 *
 *  - El banco rechazó un cheque que sí se depositó → `rechazar`, que crea un
 *    movimiento de egreso: hubo dos hechos reales y el extracto los muestra.
 *  - El depósito nunca existió, fue un error de carga → `deshacerDeposito`, que
 *    ANULA el movimiento: no hubo ningún hecho, así que inventar un egreso le
 *    mostraría al cliente una salida de plata que el banco nunca tuvo.
 *
 * Caso real (DOBA): un cheque mal cargado se "rechazó" para sacarlo y quedó un
 * egreso falso en el extracto de mayo.
 */
describe('TesChequeService.deshacerDeposito', () => {
  const chequeBase = {
    id: 'chq-1',
    empresa_id: 'emp-1',
    tipo: 'RECIBIDO',
    estado: 'DEPOSITADO',
    numero_cheque: '5792344',
    monto: 4_040_709,
    fecha_vencimiento: null as string | null,
    movimiento_id: 'mov-1',
    movimiento: { id: 'mov-1', cuenta_id: 'cta-1', monto: 4_040_709, estado: 'CONFIRMADO', tipo: 'INGRESO' },
  };

  const crear = (cheque: any = {}, { conciliado = false } = {}) => {
    const tx = {
      tes_movimientos: { update: jest.fn().mockResolvedValue({}) },
      tes_cuentas: { update: jest.fn().mockResolvedValue({}) },
      tes_cheques: { update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ ...chequeBase, ...data })) },
    };
    const prisma: any = {
      tes_cheques: { findFirst: jest.fn().mockResolvedValue({ ...chequeBase, ...cheque }) },
      tes_extracto_lineas: { findFirst: jest.fn().mockResolvedValue(conciliado ? { id: 'lin-1' } : null) },
      $transaction: jest.fn().mockImplementation((fn: any) => fn(tx)),
    };
    const service = Object.create(TesChequeService.prototype) as any;
    service.prisma = prisma;
    service.logger = { log: jest.fn(), warn: jest.fn(), error: jest.fn() };
    service.audit = { log: jest.fn().mockResolvedValue(undefined) };
    service.contabilidad = { revertirDocumento: jest.fn().mockResolvedValue(undefined) };
    return { service, prisma, tx };
  };

  it('anula el movimiento en vez de crear un egreso de reversa', async () => {
    const { service, tx } = crear();

    await service.deshacerDeposito('chq-1', 'emp-1', 'usr-1');

    expect(tx.tes_movimientos.update).toHaveBeenCalledWith(
      expect.objectContaining({ where: { id: 'mov-1' }, data: expect.objectContaining({ estado: 'ANULADO' }) }),
    );
    // Lo que NO debe pasar: un movimiento nuevo de salida.
    expect((tx as any).tes_movimientos.create).toBeUndefined();
  });

  it('devuelve el saldo de la cuenta al estado anterior', async () => {
    const { service, tx } = crear();

    await service.deshacerDeposito('chq-1', 'emp-1', 'usr-1');

    expect(tx.tes_cuentas.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: 'cta-1' },
        data: expect.objectContaining({ saldo_actual: { decrement: 4_040_709 } }),
      }),
    );
  });

  it('el cheque vuelve a En cartera y suelta el movimiento anulado', async () => {
    const { service, tx } = crear();

    await service.deshacerDeposito('chq-1', 'emp-1', 'usr-1');

    const data = tx.tes_cheques.update.mock.calls[0][0].data;
    expect(data.estado).toBe('EN_CARTERA');
    expect(data.movimiento_id).toBeNull();
  });

  it('si el cheque es a fecha futura vuelve a Diferido', async () => {
    const futuro = new Date(Date.now() + 30 * 86_400_000).toISOString().slice(0, 10);
    const { service, tx } = crear({ fecha_vencimiento: new Date(futuro) });

    await service.deshacerDeposito('chq-1', 'emp-1', 'usr-1');

    expect(tx.tes_cheques.update.mock.calls[0][0].data.estado).toBe('DIFERIDO');
  });

  it('revierte también el asiento contable del movimiento', async () => {
    const { service } = crear();

    await service.deshacerDeposito('chq-1', 'emp-1', 'usr-1');

    expect(service.contabilidad.revertirDocumento).toHaveBeenCalledWith('tes_movimientos', 'mov-1', 'usr-1');
  });

  it('no se puede deshacer si el movimiento ya fue conciliado con el banco', async () => {
    const { service } = crear({}, { conciliado: true });

    await expect(service.deshacerDeposito('chq-1', 'emp-1', 'usr-1')).rejects.toThrow(/concilia/i);
  });

  it('un cheque en cartera no tiene depósito que deshacer', async () => {
    const { service } = crear({ estado: 'EN_CARTERA', movimiento: null, movimiento_id: null });

    await expect(service.deshacerDeposito('chq-1', 'emp-1', 'usr-1')).rejects.toThrow(BadRequestException);
  });

  it('un cheque rechazado no se deshace: ahí el hecho ya ocurrió', async () => {
    const { service } = crear({ estado: 'RECHAZADO' });

    await expect(service.deshacerDeposito('chq-1', 'emp-1', 'usr-1')).rejects.toThrow(/RECHAZADO/);
  });

  it('también deshace el débito de un cheque emitido', async () => {
    const { service, tx } = crear({
      tipo: 'EMITIDO',
      estado: 'DEBITADO',
      movimiento: { id: 'mov-1', cuenta_id: 'cta-1', monto: 1_000_000, estado: 'CONFIRMADO', tipo: 'EGRESO' },
    });

    await service.deshacerDeposito('chq-1', 'emp-1', 'usr-1');

    // El débito restó del saldo: deshacerlo lo devuelve.
    expect(tx.tes_cuentas.update).toHaveBeenCalledWith(
      expect.objectContaining({ data: expect.objectContaining({ saldo_actual: { increment: 1_000_000 } }) }),
    );
  });
});
