import { SugerenciaCategoriaService, parsearArray } from './sugerencia-categoria.service';

/**
 * La IA solo sugiere. Lo que devuelve se valida contra las categorías reales de
 * la empresa antes de mostrarse, y aplicar exige que el movimiento siga con
 * categoría genérica.
 */
describe('SugerenciaCategoriaService', () => {
  const categorias = [
    { id: 'cat-prestamo', nombre: 'PRESTAMO LIC. BB', tipo: 'EGRESO', cuenta_contable_id: 'cta-prestamo' },
    { id: 'cat-desembolso', nombre: 'Desembolso préstamo BB', tipo: 'INGRESO', cuenta_contable_id: 'cta-prestamo' },
    { id: 'cat-banco', nombre: 'Gasto Bancario / Comisión', tipo: 'EGRESO', cuenta_contable_id: 'cta-gastos-banco' },
  ];
  const detalle = (id: string, tipo: string, descripcion: string, monto = 1000) => ({
    id: `det-${id}`,
    monto,
    descripcion,
    categoria: { nombre: tipo === 'INGRESO' ? 'Otro Ingreso' : 'Otro Egreso' },
    movimiento: {
      id,
      fecha: new Date('2026-08-13'),
      tipo,
      descripcion,
      referencia: null,
      cuenta: { nombre: 'DOBA S.A.', moneda: 'PYG' },
    },
  });

  const crear = (respuestaIa: string, detalles: any[]) => {
    const prisma: any = {
      tes_movimiento_det: {
        count: jest.fn().mockResolvedValue(detalles.length),
        findMany: jest.fn().mockResolvedValue(detalles),
        findFirst: jest.fn(),
        update: jest.fn().mockResolvedValue({}),
      },
      tes_categorias: { findMany: jest.fn().mockResolvedValue(categorias) },
      tes_categoria_cuenta_contable: { findMany: jest.fn().mockResolvedValue([]) },
      cont_plan_cuentas: {
        findMany: jest.fn().mockResolvedValue([
          { id: 'cta-prestamo', codigo: '2.01.02.06', descripcion: 'PRESTAMO LIC. BB' },
          { id: 'cta-gastos-banco', codigo: '11.16', descripcion: 'COMISIONES BANCARIAS' },
        ]),
      },
      cont_documentos: { findFirst: jest.fn().mockResolvedValue(null) },
    };
    const ai: any = { completar: jest.fn().mockResolvedValue({ content: respuestaIa }) };
    const conciliacion: any = {
      regenerarUno: jest.fn().mockResolvedValue({ ok: true }),
      fechaActivacionContabilidad: jest.fn().mockResolvedValue(new Date('2026-05-18')),
    };
    const recontabilizacion: any = { aplicar: jest.fn() };
    const service = new SugerenciaCategoriaService(prisma, ai, { log: jest.fn() } as any, conciliacion, recontabilizacion);
    return { service, prisma, ai, conciliacion };
  };

  it('devuelve la categoría sugerida con su cuenta contable', async () => {
    const { service } = crear(
      '```json\n[{"id":"m1","accion":"RECATEGORIZAR","categoria_id":"cat-desembolso","confianza":0.92,"motivo":"Desembolso del préstamo BB"}]\n```',
      [detalle('m1', 'INGRESO', 'PRESTAMO BB A DOBA SA', 240_000_000)],
    );
    const r = await service.sugerir('emp-1');
    expect(r.sugerencias[0]).toMatchObject({
      accion: 'RECATEGORIZAR',
      categoria_sugerida: 'Desembolso préstamo BB',
      cuenta_contable_sugerida: '2.01.02.06 PRESTAMO LIC. BB',
      confianza: 0.92,
    });
  });

  it('una categoría inventada o del tipo contrario se descarta y queda para revisar', async () => {
    const { service } = crear(
      '[{"id":"m1","accion":"RECATEGORIZAR","categoria_id":"no-existe","confianza":0.9},' +
        '{"id":"m2","accion":"RECATEGORIZAR","categoria_id":"cat-prestamo","confianza":0.9}]',
      [detalle('m1', 'EGRESO', 'algo'), detalle('m2', 'INGRESO', 'PRESTAMO')],
    );
    const r = await service.sugerir('emp-1');
    expect(r.sugerencias.map((s) => [s.accion, s.categoria_sugerida_id])).toEqual([
      ['REVISAR', null],
      ['REVISAR', null],
    ]);
  });

  it('un movimiento que la IA no devolvió queda para revisar, no se inventa nada', async () => {
    const { service } = crear('[]', [detalle('m1', 'EGRESO', 'xx')]);
    const r = await service.sugerir('emp-1');
    expect(r.sugerencias[0].accion).toBe('REVISAR');
    expect(r.sugerencias[0].motivo).toMatch(/no devolvió/);
  });

  it('sin IA configurada avisa en vez de devolver todo vacío', async () => {
    const { service } = crear('[Sin API key configurada]', [detalle('m1', 'EGRESO', 'xx')]);
    await expect(service.sugerir('emp-1')).rejects.toThrow(/no tiene IA configurada/);
  });

  it('aplicar cambia la categoría y genera el asiento si el movimiento no tenía', async () => {
    const { service, prisma, conciliacion } = crear('[]', []);
    prisma.tes_movimiento_det.findFirst.mockResolvedValue({
      id: 'det-m1',
      categoria: { codigo_interno: 'INGRESO_OTRO', nombre: 'Otro Ingreso' },
      movimiento: { id: 'm1', tipo: 'INGRESO', estado: 'CONFIRMADO' },
    });
    const r = await service.aplicar('emp-1', [{ detalle_id: 'det-m1', categoria_id: 'cat-desembolso' }], 'u1');
    expect(r.ok).toBe(1);
    expect(prisma.tes_movimiento_det.update).toHaveBeenCalledWith({ where: { id: 'det-m1' }, data: { categoria_id: 'cat-desembolso' } });
    expect(conciliacion.regenerarUno).toHaveBeenCalledWith('emp-1', 'tes_movimientos', 'm1', 'u1');
  });

  it('aplicar no toca un movimiento que ya no tiene categoría genérica', async () => {
    const { service, prisma } = crear('[]', []);
    prisma.tes_movimiento_det.findFirst.mockResolvedValue({
      id: 'det-m1',
      categoria: { codigo_interno: null, nombre: 'PRESTAMO LIC. BB' },
      movimiento: { id: 'm1', tipo: 'EGRESO', estado: 'CONFIRMADO' },
    });
    const r = await service.aplicar('emp-1', [{ detalle_id: 'det-m1', categoria_id: 'cat-banco' }]);
    expect(r.items[0]).toMatchObject({ ok: false, error: 'Ya no tiene categoría genérica' });
    expect(prisma.tes_movimiento_det.update).not.toHaveBeenCalled();
  });

  it('solo considera movimientos manuales o de extracto, desde la activación', async () => {
    const { service, prisma } = crear('[]', [detalle('m1', 'EGRESO', 'xx')]);
    await service.sugerir('emp-1');
    const where = prisma.tes_movimiento_det.findMany.mock.calls[0][0].where;
    expect(where.movimiento).toMatchObject({
      empresa_id: 'emp-1',
      OR: [{ origen_tipo: null }, { origen_tipo: 'IMPORT_EXTRACTO' }],
      fecha: { gte: new Date('2026-05-18') },
    });
  });

  it('parsearArray tolera texto alrededor y JSON roto', () => {
    expect(parsearArray('Acá va:\n[{"id":"a"}]\nListo')).toEqual([{ id: 'a' }]);
    expect(parsearArray('[{"id": ')).toEqual([]);
    expect(parsearArray('sin array')).toEqual([]);
  });
});
