import { cambiarFechaEmisionRecibo } from './cambiar-fecha-recibo';

/**
 * Cambiar la fecha de emisión de un recibo arrastra el asiento y los movimientos
 * de tesorería; si algo ya está cerrado (rendición, comisión liquidada,
 * conciliación, período), no toca nada.
 */
describe('cambiarFechaEmisionRecibo', () => {
  const HOY = new Date('2026-10-08T12:00:00');
  const base = { reciboId: 'rec-1', empresaId: 'emp-1', motivo: 'Se cargó con la fecha equivocada', hoy: HOY };

  const armar = (o: { periodosPorFecha?: Record<string, any>; recibo?: any; comisiones?: number; movs?: any[]; conciliados?: number; asientos?: any[]; periodoNuevo?: any; ultimo?: number } = {}) => {
    const tx = {
      recibos_cobro: { update: jest.fn() },
      tes_movimientos: { updateMany: jest.fn() },
      cont_asientos: { update: jest.fn(), findFirst: jest.fn().mockResolvedValue({ numero: o.ultimo ?? 40 }) },
    };
    const prisma: any = {
      recibos_cobro: {
        findFirst: jest.fn().mockResolvedValue(
          o.recibo === undefined
            ? { id: 'rec-1', numero_recibo: '001-001-0000540', estado: 'emitido', fecha_emision: new Date('2026-10-05T12:00:00'), rendicion_id: null }
            : o.recibo,
        ),
      },
      comisiones: { count: jest.fn().mockResolvedValue(o.comisiones ?? 0) },
      tes_movimientos: { findMany: jest.fn().mockResolvedValue(o.movs ?? [{ id: 'mov-1' }]) },
      tes_extracto_lineas: { count: jest.fn().mockResolvedValue(o.conciliados ?? 0) },
      cont_documentos: { findMany: jest.fn().mockResolvedValue([{ id: 'doc-1' }]) },
      cont_asientos: {
        findMany: jest.fn().mockResolvedValue(
          o.asientos ?? [{ id: 'as-1', numero: 120, periodo: { id: 'per-oct', mes: 10, anio: 2026, estado: 'ABIERTO' } }],
        ),
      },
      cont_periodos: {
        // Con `estado` en el where: búsqueda del período abierto destino. Sin él: chequeo de cerrado.
        findFirst: jest.fn().mockImplementation(({ where }: any) => {
          if (where.estado) return Promise.resolve(o.periodoNuevo === undefined ? { id: 'per-oct', mes: 10, anio: 2026 } : o.periodoNuevo);
          const f = where.fecha_inicio.lte.toISOString().slice(0, 10);
          return Promise.resolve((o.periodosPorFecha ?? {})[f] ?? null);
        }),
      },
      $transaction: jest.fn().mockImplementation((fn: any) => fn(tx)),
    };
    return { prisma, tx };
  };

  it('mismo mes: cambia recibo, movimiento y asiento, y el asiento conserva su número', async () => {
    const { prisma, tx } = armar();
    const r = await cambiarFechaEmisionRecibo(prisma, { ...base, fecha: '2026-10-03' });

    expect(tx.recibos_cobro.update.mock.calls[0][0].data.fecha_emision).toEqual(new Date('2026-10-03T12:00:00'));
    expect(tx.tes_movimientos.updateMany.mock.calls[0][0].data.fecha).toEqual(new Date('2026-10-03T00:00:00Z'));
    expect(tx.cont_asientos.update.mock.calls[0][0].data).toMatchObject({ periodo_id: 'per-oct', numero: 120 });
    expect(r).toMatchObject({ fecha_anterior: '2026-10-05', fecha_nueva: '2026-10-03', movimientos_tesoreria: 1 });
  });

  it('otro mes: el asiento pasa al período nuevo con el siguiente número libre', async () => {
    const { prisma, tx } = armar({ periodoNuevo: { id: 'per-sep', mes: 9, anio: 2026 }, ultimo: 57 });
    const r = await cambiarFechaEmisionRecibo(prisma, { ...base, fecha: '2026-09-30' });

    expect(tx.cont_asientos.update.mock.calls[0][0].data).toMatchObject({ periodo_id: 'per-sep', numero: 58 });
    expect(r.asientos[0]).toEqual({ numero_anterior: 120, numero: 58, periodo_anterior: '10/2026', periodo_nuevo: '9/2026' });
  });

  const rechaza = async (o: any, fecha: string, patron: RegExp, extra: any = {}) => {
    const { prisma, tx } = armar(o);
    await expect(cambiarFechaEmisionRecibo(prisma, { ...base, ...extra, fecha })).rejects.toThrow(patron);
    expect(tx.recibos_cobro.update).not.toHaveBeenCalled();
  };

  it('sin motivo', () => rechaza({}, '2026-10-03', /motivo/, { motivo: ' ' }));
  it('fecha futura', () => rechaza({}, '2026-10-09', /futura/));
  it('misma fecha', () => rechaza({}, '2026-10-05', /ya tiene esa fecha/));
  it('recibo anulado', () =>
    rechaza({ recibo: { id: 'rec-1', estado: 'anulado', fecha_emision: new Date('2026-10-05T12:00:00') } }, '2026-10-03', /anulado/));
  it('recibo rendido', () =>
    rechaza({ recibo: { id: 'rec-1', estado: 'emitido', fecha_emision: new Date('2026-10-05T12:00:00'), rendicion_id: 'ren-1' } }, '2026-10-03', /rendición/));
  it('comisión liquidada', () => rechaza({ comisiones: 1 }, '2026-10-03', /liquidada/));
  it('cobro conciliado con el extracto', () => rechaza({ conciliados: 1 }, '2026-10-03', /conciliado/));
  it('asiento en período cerrado', () =>
    rechaza({ asientos: [{ id: 'as-1', numero: 5, periodo: { id: 'per-ago', mes: 8, anio: 2026, estado: 'CERRADO' } }] }, '2026-10-03', /CERRADO/));
  it('fecha nueva en un período cerrado, aunque el recibo no tenga asiento', () =>
    rechaza({ asientos: [], periodosPorFecha: { '2026-08-31': { mes: 8, anio: 2026, estado: 'CERRADO' } } }, '2026-08-31', /fecha nueva .* 8\/2026, que está CERRADO/));
  it('fecha actual en un período bloqueado', () =>
    rechaza({ asientos: [], periodosPorFecha: { '2026-10-05': { mes: 10, anio: 2026, estado: 'BLOQUEADO' } } }, '2026-10-03', /fecha actual .* BLOQUEADO/));
  it('sin contabilidad (sin períodos) y sin asiento: se puede cambiar', async () => {
    const { prisma, tx } = armar({ asientos: [] });
    prisma.cont_documentos.findMany.mockResolvedValue([]);
    await cambiarFechaEmisionRecibo(prisma, { ...base, fecha: '2026-03-15' });
    expect(tx.recibos_cobro.update).toHaveBeenCalled();
  });
  it('fecha nueva sin período abierto', () => rechaza({ periodoNuevo: null }, '2026-10-03', /período contable abierto/));
});
