import { FaltaMapeoError } from './falta-mapeo.error';
import { crearIntegracionMock as crear } from './__helpers__/integracion-mock';

/**
 * Regla de la casa (spec 2026-10-13, "el contador decide las cuentas"): si falta
 * un mapeo, el documento queda sin asiento, con constancia y aviso, y la
 * operación del usuario no falla. Nunca se cae a otra cuenta.
 */
describe('sin respaldos silenciosos', () => {
  it('la orden de pago con cheque y sin CHEQUES_A_PAGAR no asienta, no falla y avisa', async () => {
    const orden = {
      id: 'op1', empresa_id: 'emp', estado: 'PAGADO', anulado: false, monto_total: 100, moneda: { codigo: 'PYG' }, cotizacion: null,
      fecha_pago: new Date('2026-10-13'), numero_orden_pago: 'OP-1', proveedor_id: 'p1', usuario_id: 'u1',
      orden_pago_proveedor_det: [{ monto: 100, medio_pago_id: 'mp-cheque', medio_pago: { codigo: 2 }, cuenta_bancaria_id: 'tes1', cuentas_pagar: { cuenta_pasivo_id: 'pas', compra_id: null } }],
    };
    const s = crear({
      orden_pago_proveedor_cab: { findUnique: jest.fn().mockResolvedValue(orden) },
      tes_reglas: { findMany: jest.fn().mockResolvedValue([]) },
      tes_cuentas: { findMany: jest.fn().mockResolvedValue([{ id: 'tes1', cuenta_contable_id: 'cta-banco' }]) },
    }, ['CHEQUES_A_PAGAR']);

    await expect(s.integrarOrdenPago('op1')).resolves.toBeUndefined();
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
      empresaId: 'emp', origenTipo: 'orden_pago_proveedor_cab', origenId: 'op1', conceptos: ['CHEQUES_A_PAGAR'],
    });
    // Queda el documento en borrador, como cualquier OP pendiente.
    expect(s.prisma.cont_documentos.upsert.mock.calls[0][0].create.estado).toBe('BORRADOR');
  });

  it('el pago de comisiones sin PAGO_COMISIONES_FONDOS no cae a caja', async () => {
    const s = crear({
      liquidaciones_comisiones: {
        findUnique: jest.fn().mockResolvedValue({
          id: 'l1', empresa_id: 'emp', estado: 'pagada', total_comisiones: 100, fecha_pago: new Date('2026-10-13'),
          vendedor_cobrador_id: 'v1', periodo_desde: new Date('2026-10-01'), periodo_hasta: new Date('2026-10-13'),
        }),
      },
      vendedores_cobradores: { findUnique: jest.fn().mockResolvedValue({ nombre: 'A', apellido: 'B', regimen_comision: 'EMPLEADO' }) },
    }, ['PAGO_COMISIONES_FONDOS']);
    await s.integrarPagoComisiones('l1', 'emp');
    expect(s.mapeo.getCuentaPorConcepto).not.toHaveBeenCalledWith('emp', 'CAJA_GENERAL');
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo.mock.calls[0][0].conceptos).toEqual(['PAGO_COMISIONES_FONDOS']);
  });

  it('un proveedor del exterior sin PROVEEDORES_EXTERIOR no cae a PROVEEDORES', async () => {
    const s = crear({ proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_EXTERIOR', cuenta_contable_id: null }) } }, ['PROVEEDORES_EXTERIOR']);
    await expect(s.resolverCuentaPasivo('p1', 'emp')).rejects.toBeInstanceOf(FaltaMapeoError);
    expect(s.mapeo.getCuentaPorConcepto).not.toHaveBeenCalledWith('emp', 'PROVEEDORES');
  });

  it('otros errores se siguen propagando', async () => {
    const s = crear({ gasto_cab: { findUnique: jest.fn().mockRejectedValue(new Error('db caída')) } });
    await expect(s.integrarGasto('g1')).rejects.toThrow('db caída');
  });

  it('si registrar el aviso falla, la operación igual no falla', async () => {
    const s = crear({ proveedores: { findFirst: jest.fn().mockResolvedValue(null) } }, ['CLIENTES']);
    s.avisosMapeo.registrarFaltaMapeo.mockRejectedValue(new Error('alertas caídas'));
    const pago = { id: 'b1', empresa_id: 'emp', estado: 'aprobado', monto: 100, shop_process_id: 1, origen_modulo: 'X', fecha_confirmacion: new Date('2026-10-13'), empresa: { bancard_config: { cuenta_contable_por_liquidar: 'cta-bancard' } } };
    s.prisma.pago_bancard = { findUnique: jest.fn().mockResolvedValue(pago) };
    await expect(s.integrarPagoBancard('b1')).resolves.toBeUndefined();
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.logger.error).toHaveBeenCalled();
  });

  it('el despacho con anticipo y sin ANTICIPO_DESPACHO no se acredita al proveedor', async () => {
    const s = crear({
      imp_despachos: {
        findUnique: jest.fn().mockResolvedValue({
          id: 'd1', empresa_id: 'emp', estado: 'CONFIRMADO', embarque_id: 'e1', numero_despacho: 'D-1', iva_importacion_gs: 0,
          fecha_despacho: new Date('2026-10-13'), usuario_id: null, embarque: { proveedor_id: 'p1', numero: 'E-1' },
        }),
      },
      imp_componentes_costo: { findMany: jest.fn().mockResolvedValue([{ importe_gs: 1000, concepto: { afecta_costo: true } }]) },
      tes_movimientos: { findFirst: jest.fn().mockResolvedValue({ id: 'm1' }) },
    }, ['ANTICIPO_DESPACHO']);
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES_EXTERIOR');
    await s.integrarCierreDespacho('d1');
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo.mock.calls[0][0]).toEqual({
      empresaId: 'emp', origenTipo: 'imp_despacho', origenId: 'd1', conceptos: ['ANTICIPO_DESPACHO'],
    });
  });

  it('la liquidación de sueldos con IPS y sin sus conceptos avisa los que faltan juntos', async () => {
    const s = crear({
      rrhh_liquidaciones_cabecera: {
        findUnique: jest.fn().mockResolvedValue({
          id: 'r1', empresa_id: 'emp', estado: 'CERRADA', periodo_anio: 2026, periodo_mes: 9, tipo: 'MENSUAL',
          resumenes: [{ total_ingresos: 1000, ips_obrero: 90, ips_patronal: 165, ips_admin: 0, neto_a_pagar: 910, total_egresos: 90, empleado: { id: 'x', centro_costo_id: null } }],
        }),
      },
      cont_centros_costo: { findMany: jest.fn().mockResolvedValue([]) },
    }, ['APORTE_PATRONAL_IPS', 'IPS_A_DEPOSITAR', 'APORTE_ADMIN_IPS']);
    await s.integrarLiquidacionRrhh('r1');
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    // El admin es 0: no hace falta y no se pide.
    expect(s.avisosMapeo.registrarFaltaMapeo.mock.calls[0][0].conceptos).toEqual(['APORTE_PATRONAL_IPS', 'IPS_A_DEPOSITAR']);
  });

  it('el devengamiento con IVA y sin IVA_CREDITO_10 no asienta todo como gasto', async () => {
    const s = crear({
      liquidaciones_comisiones: {
        findUnique: jest.fn().mockResolvedValue({
          id: 'l1', total_comisiones: 100, created_at: new Date('2026-10-13'), vendedor_cobrador_id: 'v1',
          periodo_desde: new Date('2026-10-01'), periodo_hasta: new Date('2026-10-13'),
        }),
      },
      vendedores_cobradores: { findUnique: jest.fn().mockResolvedValue({ nombre: 'A', apellido: 'B', regimen_comision: 'EXTERNO_IVA_ADICIONAL' }) },
    }, ['IVA_CREDITO_10']);
    await s.integrarDevengamientoComisiones('l1', 'emp');
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo.mock.calls[0][0].conceptos).toEqual(['IVA_CREDITO_10']);
  });
  it('la transferencia en moneda extranjera sin DIFERENCIA_CAMBIO_* no asienta de 2 patas: avisa', async () => {
    const fecha = new Date('2026-10-13');
    const movs: Record<string, any> = {
      mo: { id: 'mo', fecha, monto: 7_300_000, created_by: null, referencia: null, descripcion: null, cuenta: { cuenta_contable_id: 'cta-banco-gs', moneda: 'PYG', nombre: 'Banco Gs' } },
      md: { id: 'md', fecha, monto: 1_000, created_by: null, referencia: null, descripcion: null, cuenta: { cuenta_contable_id: 'cta-banco-usd', moneda: 'USD', nombre: 'Banco USD' } },
    };
    const s = crear({
      tes_movimientos: { findUnique: jest.fn().mockImplementation(({ where }: any) => Promise.resolve(movs[where.id])) },
      // Cotización del sistema 7.400 vs 7.300 del banco: hay diferencia ganada.
      cont_tipo_cambio: { findFirst: jest.fn().mockResolvedValue({ tasa: 7_400 }) },
    }, ['DIFERENCIA_CAMBIO_GANADA']);

    await expect(s.integrarTransferenciaTes('mo', 'md', 'tr1', 'emp')).resolves.toBeUndefined();
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
      empresaId: 'emp', origenTipo: 'tes_transferencias', origenId: 'tr1', conceptos: ['DIFERENCIA_CAMBIO_GANADA'],
    });
  });

  it('el egreso imputado a una importación sin su concepto no cae a la categoría: queda pendiente con aviso', async () => {
    const s = crear({
      tes_movimientos: {
        findUnique: jest.fn().mockResolvedValue({
          id: 'm1', empresa_id: 'emp', tipo: 'EGRESO', origen_tipo: null, fecha: new Date('2026-10-13'), referencia: null, descripcion: 'Pago proveedor exterior',
          embarque_id: 'e1', tipo_pago_importacion: 'PROVEEDOR', created_by: null,
          cuenta: { cuenta_contable_id: 'cta-banco', moneda: 'PYG' },
          detalles: [{ categoria_id: 'cat1', monto: 1000, cuenta_contable_id: null, categoria: { nombre: 'Pagos', cuenta_contable_id: 'cta-categoria' } }],
        }),
      },
      tes_categoria_cuenta_contable: { findMany: jest.fn().mockResolvedValue([]) },
    }, ['IMPORTACION_PAGO_PROVEEDOR']);

    await expect(s.integrarMovimientoTesoreria('m1')).resolves.toBeUndefined();
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
      empresaId: 'emp', origenTipo: 'tes_movimientos', origenId: 'm1', conceptos: ['IMPORTACION_PAGO_PROVEEDOR'],
    });
  });

  it('en el recibo, un error que no es de mapeo no se registra como "falta mapear"', async () => {
    const s = crear({
      recibos_cobro: { findUnique: jest.fn().mockResolvedValue({
        id: 'r1', estado: 'emitido', modo: 'MULTI', numero_recibo: '1', fecha_emision: new Date('2026-10-13'), usuario_id: null,
        moneda: { codigo: 'PYG' }, monto_total: 100, total_facturas: 100, facturas_multi: [], nc_aplicadas_multi: [],
        retenciones_multi: [], medios_pago_multi: [{ medio: 'EFECTIVO', monto: 100 }], facturas_compra_aplicadas: [], gastos_asociados: [],
      }) },
    });
    s.mapeo.getCuentaPorConcepto.mockImplementation((_e: string, c: string) =>
      c === 'RETENCION_IVA_FAVOR' ? Promise.reject(new Error('db caída')) : Promise.resolve(`cta-${c}`),
    );
    await expect(s.integrarReciboMulti('r1', 'emp')).rejects.toThrow('db caída');
    expect(s.avisosMapeo.registrarFaltaMapeo).not.toHaveBeenCalled();
  });
});
