import { BadRequestException } from '@nestjs/common';
import { TesConciliacionService } from './tes-conciliacion.service';

describe('TesConciliacionService', () => {
  const createService = (overrides: Record<string, any> = {}) => {
    const prisma = {
      tes_extracto_lineas: {
        findFirst: jest.fn(),
        update: jest.fn(),
        findMany: jest.fn().mockResolvedValue([{ conciliado: true, sin_correspondencia: false }]),
      },
      tes_extracto_bancario: {
        update: jest.fn().mockResolvedValue(undefined),
      },
      tes_movimientos: {
        findFirst: jest.fn(),
      },
      ...overrides,
    };

    const aiProvider = { completar: jest.fn() };
    const contabilidad = { integrarMovimientoTesoreria: jest.fn() };
    const service = new TesConciliacionService(prisma as any, aiProvider as any, contabilidad as any);
    return { service, prisma };
  };

  it('conciliarLinea valida cuenta/tipo/monto y concilia correctamente', async () => {
    const { service, prisma } = createService();
    prisma.tes_extracto_lineas.findFirst
      .mockResolvedValueOnce({
        id: 'linea-1',
        extracto_id: 'ext-1',
        conciliado: false,
        debito: 150000,
        credito: 0,
        extracto: { cuenta_id: 'cuenta-1' },
      })
      .mockResolvedValueOnce(null);
    prisma.tes_movimientos.findFirst.mockResolvedValue({
      id: 'mov-1',
      empresa_id: 'emp-1',
      cuenta_id: 'cuenta-1',
      tipo: 'EGRESO',
      estado: 'CONFIRMADO',
      monto: 150000,
    });
    prisma.tes_extracto_lineas.update.mockResolvedValue(undefined);

    await expect(service.conciliarLinea('emp-1', 'linea-1', 'mov-1')).resolves.toEqual({ ok: true });

    expect(prisma.tes_extracto_lineas.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: 'linea-1' },
        data: { conciliado: true, movimiento_id: 'mov-1', sin_correspondencia: false },
      }),
    );
  });

  it('conciliarLinea rechaza movimiento de otra cuenta', async () => {
    const { service, prisma } = createService();
    prisma.tes_extracto_lineas.findFirst.mockResolvedValue({
      id: 'linea-1',
      extracto_id: 'ext-1',
      conciliado: false,
      debito: 1000,
      credito: 0,
      extracto: { cuenta_id: 'cuenta-1' },
    });
    prisma.tes_movimientos.findFirst.mockResolvedValue({
      id: 'mov-1',
      empresa_id: 'emp-1',
      cuenta_id: 'cuenta-2',
      tipo: 'EGRESO',
      estado: 'CONFIRMADO',
      monto: 1000,
    });

    await expect(service.conciliarLinea('emp-1', 'linea-1', 'mov-1')).rejects.toThrow(BadRequestException);
  });

  it('conciliarLinea rechaza movimiento ya conciliado en otra línea', async () => {
    const { service, prisma } = createService();
    prisma.tes_extracto_lineas.findFirst
      .mockResolvedValueOnce({
        id: 'linea-1',
        extracto_id: 'ext-1',
        conciliado: false,
        debito: 1000,
        credito: 0,
        extracto: { cuenta_id: 'cuenta-1' },
      })
      .mockResolvedValueOnce({ id: 'linea-otra', extracto_id: 'ext-2' });
    prisma.tes_movimientos.findFirst.mockResolvedValue({
      id: 'mov-1',
      empresa_id: 'emp-1',
      cuenta_id: 'cuenta-1',
      tipo: 'EGRESO',
      estado: 'CONFIRMADO',
      monto: 1000,
    });

    await expect(service.conciliarLinea('emp-1', 'linea-1', 'mov-1')).rejects.toThrow(BadRequestException);
  });

  it('actualizarLinea rechaza débito y crédito simultáneos', async () => {
    const { service, prisma } = createService();
    prisma.tes_extracto_lineas.findFirst.mockResolvedValue({
      id: 'linea-1',
      extracto_id: 'ext-1',
      conciliado: false,
    });

    await expect(
      service.actualizarLinea('emp-1', 'linea-1', { debito: 100, credito: 100 }),
    ).rejects.toThrow(BadRequestException);
  });

  describe('autoConciliarExtracto', () => {
    const createAutoService = (overrides: Record<string, any> = {}) => {
      const prisma = {
        tes_extracto_bancario: {
          findFirst: jest.fn().mockResolvedValue({
            id: 'ext-1',
            empresa_id: 'emp-1',
            cuenta_id: 'cuenta-1',
            periodo_desde: new Date('2026-04-01'),
            periodo_hasta: new Date('2026-04-30'),
          }),
          update: jest.fn().mockResolvedValue(undefined),
        },
        tes_extracto_lineas: {
          findMany: jest
            .fn()
            .mockResolvedValue([{ conciliado: true, sin_correspondencia: false }])
            .mockResolvedValueOnce([
              {
                id: 'linea-1',
                extracto_id: 'ext-1',
                fecha: new Date('2026-04-10'),
                descripcion: 'TRANSFERENCIA RECIBIDA',
                referencia: 'REF-123',
                debito: 0,
                credito: 1000,
                conciliado: false,
                sin_correspondencia: false,
              },
            ])
            .mockResolvedValueOnce([]),
          update: jest.fn().mockResolvedValue(undefined),
        },
        tes_movimientos: {
          findMany: jest.fn().mockResolvedValue([
            {
              id: 'mov-1',
              fecha: new Date('2026-04-10'),
              tipo: 'INGRESO',
              monto: 1000,
              descripcion: 'Transferencia recibida',
              referencia: 'REF-123',
              created_at: new Date('2026-04-10'),
            },
          ]),
          create: jest.fn().mockResolvedValue({ id: 'mov-auto-1' }),
        },
        tes_movimiento_det: {
          create: jest.fn().mockResolvedValue(undefined),
        },
        tes_cuentas: {
          update: jest.fn().mockResolvedValue(undefined),
        },
        tes_categorias: {
          findFirst: jest.fn().mockResolvedValue({ id: 'cat-1' }),
          create: jest.fn().mockResolvedValue({ id: 'cat-empresa' }),
        },
        $transaction: jest.fn(async (cb: any) => cb(prisma)),
        ...overrides,
      };
      const aiProvider = { completar: jest.fn() };
      const contabilidad = { integrarMovimientoTesoreria: jest.fn().mockResolvedValue(undefined) };
      const service = new TesConciliacionService(prisma as any, aiProvider as any, contabilidad as any);
      jest.spyOn(service, 'getExtractoDetalle').mockResolvedValue({ id: 'ext-1' } as any);
      return { service, prisma };
    };

    it('conciliación automática realiza match exacto por monto/tipo/fecha', async () => {
      const { service, prisma } = createAutoService();

      const res = await service.autoConciliarExtracto('emp-1', 'ext-1', {}, 'u-1');

      expect(res.ok).toBe(true);
      expect(res.resumen.conciliadas_exactas).toBe(1);
      expect(prisma.tes_extracto_lineas.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'linea-1' },
          data: expect.objectContaining({ conciliado: true, movimiento_id: 'mov-1' }),
        }),
      );
    });

    it('auto-crea movimiento para comisión bancaria sin movimiento origen', async () => {
      const { service, prisma } = createAutoService({
        tes_extracto_lineas: {
          findMany: jest
            .fn()
            .mockResolvedValue([{ conciliado: true, sin_correspondencia: false }])
            .mockResolvedValueOnce([
              {
                id: 'linea-com',
                extracto_id: 'ext-1',
                fecha: new Date('2026-04-11'),
                descripcion: 'Comisión mantenimiento cuenta',
                referencia: null,
                debito: 15000,
                credito: 0,
                conciliado: false,
                sin_correspondencia: false,
              },
            ])
            .mockResolvedValueOnce([]),
          update: jest.fn().mockResolvedValue(undefined),
        },
        tes_movimientos: {
          findMany: jest.fn().mockResolvedValue([]),
          create: jest.fn().mockResolvedValue({ id: 'mov-auto-com' }),
        },
      });

      const res = await service.autoConciliarExtracto('emp-1', 'ext-1', { auto_crear_comisiones: true }, 'u-1');

      expect(res.resumen.auto_creadas_comision).toBe(1);
      expect(prisma.tes_movimientos.create).toHaveBeenCalled();
      expect(prisma.tes_extracto_lineas.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'linea-com' },
          data: expect.objectContaining({ conciliado: true, movimiento_id: 'mov-auto-com' }),
        }),
      );
    });

    it('auto-crea movimiento para línea ITF sin movimiento origen', async () => {
      const { service, prisma } = createAutoService({
        tes_extracto_lineas: {
          findMany: jest
            .fn()
            .mockResolvedValue([{ conciliado: true, sin_correspondencia: false }])
            .mockResolvedValueOnce([
              {
                id: 'linea-itf',
                extracto_id: 'ext-1',
                fecha: new Date('2026-04-12'),
                descripcion: 'ITF débito automático',
                referencia: null,
                debito: 5000,
                credito: 0,
                conciliado: false,
                sin_correspondencia: false,
              },
            ])
            .mockResolvedValueOnce([]),
          update: jest.fn().mockResolvedValue(undefined),
        },
        tes_movimientos: {
          findMany: jest.fn().mockResolvedValue([]),
          create: jest.fn().mockResolvedValue({ id: 'mov-auto-itf' }),
        },
      });

      const res = await service.autoConciliarExtracto('emp-1', 'ext-1', { auto_crear_itf: true }, 'u-1');

      expect(res.resumen.auto_creadas_itf).toBe(1);
      expect(prisma.tes_movimientos.create).toHaveBeenCalled();
      expect(prisma.tes_extracto_lineas.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'linea-itf' },
          data: expect.objectContaining({ conciliado: true, movimiento_id: 'mov-auto-itf' }),
        }),
      );
    });
  });
});
