import { FaltaMapeoError } from 'src/contabilidad/services/falta-mapeo.error';
import { RendicionViaticosContabilidadService } from './rendicion-viaticos-contabilidad.service';
import { conResolvedor } from 'src/contabilidad/services/__helpers__/integracion-mock';

// Viáticos sin respaldos (spec 2026-10-13): ANTICIPO_VIATICO no cae a ANTICIPO_PERSONAL
// ni BANCO a CAJA_GENERAL. Falta mapeo → sin asiento, con aviso, sin romper la operación.
const armar = (faltan: string[], extra: { adelanto?: any; devolucion?: any; mapeoError?: Error } = {}) => {
  const prisma: any = {
    suscripcion_modulos: { findFirst: jest.fn().mockResolvedValue({ id: 'sm' }) },
    rendicion_adelanto: {
      findFirst: jest.fn().mockResolvedValue({
        id: 'ad1',
        monto: 500,
        fecha: new Date('2026-10-13'),
        cont_documento_id: null,
        rendicion: { numero_rendicion: 'RV-1', empleado: { nombres: 'Ana', apellidos: 'Pérez' } },
        tes_cuenta: { id: 'tes1', cuenta_contable_id: null, nombre: 'Banco' },
        ...extra.adelanto,
      }),
    },
    rendicion_devolucion: {
      findFirst: jest.fn().mockResolvedValue({
        id: 'dev1',
        monto: 200,
        fecha: new Date('2026-10-13'),
        cont_documento_id: null,
        tipo: 'REINTEGRO',
        diferido: true,
        rendicion: { numero_rendicion: 'RV-1', empleado: { nombres: 'Ana', apellidos: 'Pérez' } },
        tes_cuenta: null,
        ...extra.devolucion,
      }),
    },
    cont_plan_cuentas: { findUnique: jest.fn().mockResolvedValue({ codigo: '1.01', active: true }) },
    cont_documentos: { create: jest.fn().mockResolvedValue({ id: 'doc1' }) },
  };
  const asientos = { crearConfirmado: jest.fn().mockResolvedValue({}) };
  // Resolvedor de producción (reglas por dimensión) sobre este mapeo de mentira.
  const mapeo = conResolvedor({
    getCuentaPorConcepto: jest.fn().mockImplementation((e: string, c: string) => {
      if (extra.mapeoError) return Promise.reject(extra.mapeoError);
      return faltan.includes(c) ? Promise.reject(new FaltaMapeoError(e, [c])) : Promise.resolve(`cta-${c}`);
    }),
  });
  const periodos = { resolverPeriodoPorFecha: jest.fn().mockResolvedValue('p') };
  const audit = { log: jest.fn().mockResolvedValue(undefined) };
  const avisosMapeo = { registrarFaltaMapeo: jest.fn().mockResolvedValue(undefined) };
  const s = new RendicionViaticosContabilidadService(
    prisma,
    asientos as any,
    mapeo as any,
    periodos as any,
    audit as any,
    avisosMapeo as any,
  );
  return { s, prisma, asientos, mapeo, avisosMapeo };
};

describe('RendicionViaticosContabilidadService — sin respaldos de mapeo', () => {
  it('adelanto: usa ANTICIPO_VIATICO y BANCO', async () => {
    const { s, asientos } = armar([]);
    await s.registrarAdelanto('ad1', 'emp');
    const lineas = asientos.crearConfirmado.mock.calls[0][3].lineas;
    expect(lineas.map((l: any) => l.cuenta_id)).toEqual(['cta-ANTICIPO_VIATICO', 'cta-BANCO']);
  });

  it('adelanto sin ANTICIPO_VIATICO no cae a ANTICIPO_PERSONAL: sin asiento y con aviso', async () => {
    const { s, asientos, mapeo, avisosMapeo } = armar(['ANTICIPO_VIATICO']);
    await expect(s.registrarAdelanto('ad1', 'emp')).resolves.toBeNull();
    expect(asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(mapeo.getCuentaPorConcepto).not.toHaveBeenCalledWith('emp', 'ANTICIPO_PERSONAL');
    expect(avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
      empresaId: 'emp',
      origenTipo: 'rendicion_adelanto',
      origenId: 'ad1',
      conceptos: ['ANTICIPO_VIATICO'],
    });
  });

  it('adelanto sin cuenta de tesorería ni BANCO mapeado no cae a CAJA_GENERAL', async () => {
    const { s, asientos, mapeo, avisosMapeo } = armar(['BANCO']);
    await expect(s.registrarAdelanto('ad1', 'emp')).resolves.toBeNull();
    expect(asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(mapeo.getCuentaPorConcepto).not.toHaveBeenCalledWith('emp', 'CAJA_GENERAL');
    expect(avisosMapeo.registrarFaltaMapeo.mock.calls[0][0]).toMatchObject({
      origenTipo: 'rendicion_adelanto',
      conceptos: ['BANCO'],
    });
  });

  it('otro error del mapeo (no es falta de mapeo) se propaga como hoy', async () => {
    const { s, avisosMapeo } = armar([], { mapeoError: new Error('base caída') });
    await expect(s.registrarAdelanto('ad1', 'emp')).rejects.toThrow('base caída');
    expect(avisosMapeo.registrarFaltaMapeo).not.toHaveBeenCalled();
  });

  it('devolución sin ANTICIPO_VIATICO: sin asiento y con aviso de rendicion_devolucion', async () => {
    const { s, asientos, mapeo, avisosMapeo } = armar(['ANTICIPO_VIATICO']);
    await expect(s.registrarDevolucion('dev1', 'emp')).resolves.toBeNull();
    expect(asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(mapeo.getCuentaPorConcepto).not.toHaveBeenCalledWith('emp', 'ANTICIPO_PERSONAL');
    expect(avisosMapeo.registrarFaltaMapeo.mock.calls[0][0]).toMatchObject({
      origenTipo: 'rendicion_devolucion',
      origenId: 'dev1',
      conceptos: ['ANTICIPO_VIATICO'],
    });
  });

  it('reintegro diferido sin REINTEGRO_EMPLEADO_PENDIENTE: sin asiento y con aviso', async () => {
    const { s, asientos, avisosMapeo } = armar(['REINTEGRO_EMPLEADO_PENDIENTE']);
    await expect(s.registrarDevolucion('dev1', 'emp')).resolves.toBeNull();
    expect(asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(avisosMapeo.registrarFaltaMapeo.mock.calls[0][0]).toMatchObject({
      origenTipo: 'rendicion_devolucion',
      conceptos: ['REINTEGRO_EMPLEADO_PENDIENTE'],
    });
  });

  it('devolución a caja sin cuenta de tesorería ni BANCO: no cae a CAJA_GENERAL', async () => {
    const { s, asientos, mapeo, avisosMapeo } = armar(['BANCO'], { devolucion: { tipo: 'DEVOLUCION', diferido: false } });
    await expect(s.registrarDevolucion('dev1', 'emp')).resolves.toBeNull();
    expect(asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(mapeo.getCuentaPorConcepto).not.toHaveBeenCalledWith('emp', 'CAJA_GENERAL');
    expect(avisosMapeo.registrarFaltaMapeo.mock.calls[0][0]).toMatchObject({ conceptos: ['BANCO'] });
  });
});
