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

// Compras, gastos y despacho de una importación usan sus propios conceptos de
// mapeo (IMPORTACION_*), y el IVA de un gasto no deducible va a IVA_NO_DEDUCIBLE
// o se suma al gasto, según lo elija el contador (spec 2026-10-13).

const lineas = (s: any) => s.asientos.crearConfirmado.mock.calls[0][3].lineas;

const compra = {
  id: 'c1', empresa_id: 'emp', condicion_operacion: { codigo: 2 }, total: 1100, total_iva_10: 100, total_iva_5: 0,
  moneda: { codigo: 'PYG' }, cotizacion: null, fecha_emision: new Date('2026-10-13'), proveedor_id: 'p1', numero_factura: '1',
};
const sinConfigCompras = { config_compras: { findUnique: jest.fn().mockResolvedValue(null) } };

describe('compra cargada a una importación', () => {
  it('va a IMPORTACION_COMPRA, no a INVENTARIO', async () => {
    const s = crearIntegracionMock({
      ...sinConfigCompras,
      compra_cab: { findUnique: jest.fn().mockResolvedValue(compra) },
      imp_componentes_costo: { findFirst: jest.fn().mockResolvedValue({ id: 'comp' }) },
    });
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES_EXTERIOR');
    await s.integrarFacturaCompra('c1');
    const debe = lineas(s).filter((l: any) => l.debe_moneda > 0).map((l: any) => l.cuenta_id);
    expect(debe).toContain('cta-IMPORTACION_COMPRA');
    expect(debe).not.toContain('cta-INVENTARIO');
  });

  it('una compra común sigue yendo a INVENTARIO', async () => {
    const s = crearIntegracionMock({ ...sinConfigCompras, compra_cab: { findUnique: jest.fn().mockResolvedValue(compra) } });
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES');
    await s.integrarFacturaCompra('c1');
    expect(lineas(s)[0].cuenta_id).toBe('cta-INVENTARIO');
  });
});

describe('gasto', () => {
  const gasto = (over: any = {}) => ({
    id: 'g1', empresa_id: 'emp', anulado: false, deducible: true, forma_pago: 'CREDITO', proveedor_id: 'p1',
    moneda: { codigo: 'PYG' }, cotizacion: null, fecha_emision: new Date('2026-10-13'), total: 1100, total_iva_10: 100, total_iva_5: 0,
    gasto_det: [{ subtotal: 1100, monto_iva: 100, tipo_gasto: { cuenta_contable_id: 'cta-11.22', descripcion: 'Gastos de importación' } }],
    ...over,
  });
  const base = (g: any, extra: any = {}) => ({
    gasto_cab: { findUnique: jest.fn().mockResolvedValue(g) },
    config_compras: { findUnique: jest.fn().mockResolvedValue(null) },
    tes_reglas: { findMany: jest.fn().mockResolvedValue([]) },
    ...extra,
  });
  const deImportacion = { imp_componentes_costo: { findFirst: jest.fn().mockResolvedValue({ id: 'comp' }) } };

  it('cargado a una importación con cuenta propia va a IMPORTACION_GASTO', async () => {
    const s = crearIntegracionMock(base(gasto(), deImportacion));
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES');
    await s.integrarGasto('g1');
    expect(lineas(s).find((l: any) => l.debe_moneda === 1000).cuenta_id).toBe('cta-IMPORTACION_GASTO');
  });

  it('con SUMAR_A_PRINCIPAL conserva la cuenta del tipo de gasto (lo de hoy)', async () => {
    const s = crearIntegracionMock(base(gasto(), deImportacion), [], { IMPORTACION_GASTO: 'SUMAR_A_PRINCIPAL' });
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES');
    await s.integrarGasto('g1');
    expect(lineas(s).find((l: any) => l.debe_moneda === 1000).cuenta_id).toBe('cta-11.22');
  });

  it('no deducible con cuenta propia separa el IVA', async () => {
    const s = crearIntegracionMock(base(gasto({ deducible: false })));
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES');
    await s.integrarGasto('g1');
    const l = lineas(s);
    expect(l.find((x: any) => x.cuenta_id === 'cta-11.22').debe_moneda).toBe(1000);
    expect(l.find((x: any) => x.cuenta_id === 'cta-IVA_NO_DEDUCIBLE').debe_moneda).toBe(100);
  });

  it('no deducible con SUMAR_A_PRINCIPAL suma el IVA al gasto (lo de hoy)', async () => {
    const s = crearIntegracionMock(base(gasto({ deducible: false })), [], { IVA_NO_DEDUCIBLE: 'SUMAR_A_PRINCIPAL' });
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES');
    await s.integrarGasto('g1');
    const l = lineas(s);
    expect(l.find((x: any) => x.cuenta_id === 'cta-11.22').debe_moneda).toBe(1100);
    expect(l.find((x: any) => x.cuenta_id === 'cta-IVA_NO_DEDUCIBLE')).toBeUndefined();
  });
});

describe('despacho de importación', () => {
  const despacho = {
    id: 'd1', empresa_id: 'emp', estado: 'CONFIRMADO', embarque_id: 'e1', numero_despacho: 'D-1', iva_importacion_gs: 100,
    fecha_despacho: new Date('2026-10-13'), usuario_id: null, embarque: { numero: 'E-1', proveedor_id: 'p1' },
  };
  const componentes = [
    { importe_gs: 1000, concepto: { afecta_costo: true } },
    { importe_gs: 100, concepto: { afecta_costo: true } }, // IVA de importación
    { importe_gs: 50, concepto: { afecta_costo: false } }, // anticipo IRE
  ];
  const mock = (faltan: string[] = [], anticipo: any = null) => {
    const s = crearIntegracionMock({
      imp_despachos: { findUnique: jest.fn().mockResolvedValue(despacho) },
      imp_componentes_costo: { findMany: jest.fn().mockResolvedValue(componentes) },
      tes_movimientos: { findFirst: jest.fn().mockResolvedValue(anticipo) },
    }, faltan);
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES_EXTERIOR');
    return s;
  };

  it('el costo va a IMPORTACION_TRIBUTOS y el IRE a IMPORTACION_ANTICIPO_IRE', async () => {
    const s = mock();
    await s.integrarCierreDespacho('d1');
    const l = lineas(s);
    expect(l.find((x: any) => x.cuenta_id === 'cta-IMPORTACION_TRIBUTOS').debe_moneda).toBe(1000);
    expect(l.find((x: any) => x.cuenta_id === 'cta-IMPORTACION_ANTICIPO_IRE').debe_moneda).toBe(50);
    expect(l.map((x: any) => x.cuenta_id)).not.toContain('cta-RETENCION_RENTA_FAVOR');
  });

  it('sin IMPORTACION_ANTICIPO_IRE no manda el IRE al costo: queda pendiente con aviso', async () => {
    const s = mock(['IMPORTACION_ANTICIPO_IRE']);
    await expect(s.integrarCierreDespacho('d1')).resolves.toBeUndefined();
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
      empresaId: 'emp', origenTipo: 'imp_despacho', origenId: 'd1', conceptos: ['IMPORTACION_ANTICIPO_IRE'],
    });
  });

  it('sin cuenta puente no aplica el anticipo de despacho aunque exista (lógica de anticipo intacta)', async () => {
    const s = mock(['IMPORTACIONES_EN_CURSO'], { id: 'a1' });
    await s.integrarCierreDespacho('d1');
    expect(s.prisma.tes_movimientos.findFirst).not.toHaveBeenCalled();
    expect(lineas(s).find((x: any) => x.haber_moneda > 0).cuenta_id).toBe('cta-PROVEEDORES_EXTERIOR');
  });

  it('puente mapeada a una cuenta inutilizable: no se trata como "sin puente", queda pendiente con aviso', async () => {
    const s = mock([], { id: 'a1' });
    const original = s.mapeo.getCuentaPorConcepto.getMockImplementation();
    s.mapeo.getCuentaPorConcepto.mockImplementation((e: string, c: string) =>
      c === 'IMPORTACIONES_EN_CURSO'
        ? Promise.reject(new FaltaMapeoError(e, [c], 'mapeado a 1.1.4, que es de agrupación', 'CUENTA_INUTILIZABLE', '1.1.4'))
        : original(e, c),
    );
    await expect(s.integrarCierreDespacho('d1')).resolves.toBeUndefined();
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    expect(s.avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
      empresaId: 'emp', origenTipo: 'imp_despacho', origenId: 'd1', conceptos: ['IMPORTACIONES_EN_CURSO'], motivo: 'CUENTA_INUTILIZABLE',
    });
  });

  it('un error que no es de mapeo al leer la puente no se traga como "sin puente"', async () => {
    const s = mock([], { id: 'a1' });
    const original = s.mapeo.getCuentaPorConcepto.getMockImplementation();
    s.mapeo.getCuentaPorConcepto.mockImplementation((e: string, c: string) =>
      c === 'IMPORTACIONES_EN_CURSO' ? Promise.reject(new Error('conexión caída')) : original(e, c),
    );
    await expect(s.integrarCierreDespacho('d1')).rejects.toThrow('conexión caída');
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
  });

  it('con cuenta puente y anticipo, la contrapartida es ANTICIPO_DESPACHO', async () => {
    const s = mock([], { id: 'a1' });
    await s.integrarCierreDespacho('d1');
    expect(lineas(s).find((x: any) => x.haber_moneda > 0).cuenta_id).toBe('cta-ANTICIPO_DESPACHO');
  });
});

describe('egreso de tesorería imputado a una importación', () => {
  const movimiento = (tipo: string) => ({
    id: 'm1', empresa_id: 'emp', tipo: 'EGRESO', origen_tipo: null, fecha: new Date('2026-10-13'), referencia: null, descripcion: 'Pago importación',
    embarque_id: 'e1', tipo_pago_importacion: tipo, 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' } }],
  });
  const mock = (tipo: string) =>
    crearIntegracionMock({
      tes_movimientos: { findUnique: jest.fn().mockResolvedValue(movimiento(tipo)) },
      tes_categoria_cuenta_contable: { findMany: jest.fn().mockResolvedValue([]) },
    });

  it('el pago al proveedor del exterior va a IMPORTACION_PAGO_PROVEEDOR', async () => {
    const s = mock('PROVEEDOR');
    await s.integrarMovimientoTesoreria('m1');
    expect(lineas(s).find((l: any) => l.debe_moneda > 0).cuenta_id).toBe('cta-IMPORTACION_PAGO_PROVEEDOR');
  });

  it('el anticipo al despachante sigue yendo a ANTICIPO_DESPACHO', async () => {
    const s = mock('DESPACHO');
    await s.integrarMovimientoTesoreria('m1');
    expect(lineas(s).find((l: any) => l.debe_moneda > 0).cuenta_id).toBe('cta-ANTICIPO_DESPACHO');
  });
});

describe('nota de crédito de compra', () => {
  const nc = {
    id: 'nc1', empresa_id: 'emp', compra_id: 'c1', proveedor_id: 'p1', moneda: { codigo: 'PYG' }, cotizacion: null,
    fecha_emision: new Date('2026-10-13'), total: 1100, total_iva_10: 100, total_iva_5: 0, establecimiento: '001', punto_expedicion: '001', numero: '1',
  };
  const mock = (componente: any) => {
    const s = crearIntegracionMock({
      nota_credito_compra_cab: { findUnique: jest.fn().mockResolvedValue(nc) },
      imp_componentes_costo: { findFirst: jest.fn().mockResolvedValue(componente) },
    });
    s.resolverCuentaPasivo = jest.fn().mockResolvedValue('cta-PROVEEDORES_EXTERIOR');
    return s;
  };

  it('de una compra cargada a una importación acredita IMPORTACION_COMPRA (la misma cuenta que la compra)', async () => {
    const s = mock({ id: 'comp' });
    await s.integrarNotaCreditoCompra('nc1');
    const haber = lineas(s).filter((l: any) => l.haber_moneda > 0);
    expect(haber.find((l: any) => l.haber_moneda === 1000).cuenta_id).toBe('cta-IMPORTACION_COMPRA');
    expect(haber.map((l: any) => l.cuenta_id)).not.toContain('cta-INVENTARIO');
    expect(s.prisma.imp_componentes_costo.findFirst).toHaveBeenCalledWith({ where: { compra_id: 'c1', activo: true }, select: { id: true } });
  });

  it('de una compra común sigue acreditando INVENTARIO', async () => {
    const s = mock(null);
    await s.integrarNotaCreditoCompra('nc1');
    expect(lineas(s).find((l: any) => l.haber_moneda === 1000).cuenta_id).toBe('cta-INVENTARIO');
  });

  it('sin compra asociada acredita INVENTARIO', async () => {
    const s = mock({ id: 'comp' });
    s.prisma.nota_credito_compra_cab.findUnique.mockResolvedValue({ ...nc, compra_id: null });
    await s.integrarNotaCreditoCompra('nc1');
    expect(lineas(s).find((l: any) => l.haber_moneda === 1000).cuenta_id).toBe('cta-INVENTARIO');
    expect(s.prisma.imp_componentes_costo.findFirst).not.toHaveBeenCalled();
  });
});
