import { BadRequestException } from '@nestjs/common';
import { cumpleCondiciones, elegirRegla, validarCondiciones, usaDimensionesDeItem, fraseRegla, DIMENSIONES_POR_ORIGEN, CONCEPTOS_CON_APERTURA_ITEM } from './reglas-mapeo';

const r = (over: any) => ({ id: 'r', concepto: 'VENTAS_10', orden: 1, modo: 'CUENTA', cuenta_id: 'c', estado: 'ACTIVA',
  vigente_desde: new Date('2026-01-01'), vigente_hasta: null, condiciones: [{ dimension: 'MONEDA', operador: 'EN', valores: ['USD'] }], ...over });

describe('cumpleCondiciones', () => {
  it('EN y NO_EN', () => {
    expect(cumpleCondiciones([{ dimension: 'MONEDA', operador: 'EN', valores: ['USD'] }], { MONEDA: 'USD' })).toBe(true);
    expect(cumpleCondiciones([{ dimension: 'MONEDA', operador: 'NO_EN', valores: ['USD'] }], { MONEDA: 'USD' })).toBe(false);
  });
  it('todas las condiciones (Y)', () => {
    const c = [{ dimension: 'MONEDA', operador: 'EN', valores: ['USD'] }, { dimension: 'SUCURSAL', operador: 'EN', valores: ['s1'] }] as any;
    expect(cumpleCondiciones(c, { MONEDA: 'USD', SUCURSAL: 's2' })).toBe(false);
    expect(cumpleCondiciones(c, { MONEDA: 'USD', SUCURSAL: 's1' })).toBe(true);
  });
  it('dimensión que el documento no tiene: la regla no aplica (ni con NO_EN)', () => {
    expect(cumpleCondiciones([{ dimension: 'CATEGORIA_PRODUCTO', operador: 'NO_EN', valores: ['x'] }], { MONEDA: 'PYG' })).toBe(false);
  });
  it('[RF-1] la categoría padre cubre a las hijas', () => {
    const ancestros = new Map([['nieta', ['nieta', 'hija', 'padre']]]);
    expect(cumpleCondiciones([{ dimension: 'CATEGORIA_PRODUCTO', operador: 'EN', valores: ['padre'] }], { CATEGORIA_PRODUCTO: 'nieta' }, ancestros)).toBe(true);
    expect(cumpleCondiciones([{ dimension: 'CATEGORIA_PRODUCTO', operador: 'NO_EN', valores: ['padre'] }], { CATEGORIA_PRODUCTO: 'nieta' }, ancestros)).toBe(false);
  });
});

describe('elegirRegla', () => {
  it('gana la primera por orden que cumple', () => {
    const a = r({ id: 'a', orden: 2 }); const b = r({ id: 'b', orden: 1 });
    expect(elegirRegla([a, b], new Date('2026-10-13'), { MONEDA: 'USD' })?.id).toBe('b');
  });
  it('[RF-2] vigencia por fecha contable, con bordes', () => {
    const v = r({ vigente_desde: new Date('2026-10-01'), vigente_hasta: new Date('2026-10-31') });
    expect(elegirRegla([v], new Date('2026-09-30'), { MONEDA: 'USD' })).toBeNull();
    expect(elegirRegla([v], new Date('2026-10-01'), { MONEDA: 'USD' })?.id).toBe('r');
    expect(elegirRegla([v], new Date('2026-10-31'), { MONEDA: 'USD' })).toBeNull(); // vigente_hasta es exclusivo
  });
  it('borrador solo con incluirBorradores; retirada nunca', () => {
    expect(elegirRegla([r({ estado: 'BORRADOR' })], new Date('2026-10-13'), { MONEDA: 'USD' })).toBeNull();
    expect(elegirRegla([r({ estado: 'BORRADOR' })], new Date('2026-10-13'), { MONEDA: 'USD' }, { incluirBorradores: true })?.id).toBe('r');
    expect(elegirRegla([r({ estado: 'RETIRADA' })], new Date('2026-10-13'), { MONEDA: 'USD' }, { incluirBorradores: true })).toBeNull();
  });
  it('en la vista previa un borrador aplica sin mirar su vigencia; activas y retiradas la siguen respetando', () => {
    const fecha = new Date('2025-03-10'); // documento viejo, antes de cualquier vigencia
    const vig = { vigente_desde: new Date('2026-10-14'), vigente_hasta: null };
    expect(elegirRegla([r({ estado: 'BORRADOR', ...vig })], fecha, { MONEDA: 'USD' }, { incluirBorradores: true })?.id).toBe('r');
    // Sin incluirBorradores (asientos reales) el borrador sigue sin aplicar.
    expect(elegirRegla([r({ estado: 'BORRADOR', ...vig })], fecha, { MONEDA: 'USD' })).toBeNull();
    expect(elegirRegla([r({ estado: 'ACTIVA', ...vig })], fecha, { MONEDA: 'USD' }, { incluirBorradores: true })).toBeNull();
    const retirada = r({ estado: 'RETIRADA', vigente_desde: new Date('2026-01-01'), vigente_hasta: new Date('2026-06-01') });
    expect(elegirRegla([retirada], fecha, { MONEDA: 'USD' }, { incluirBorradores: true })).toBeNull();
    // El orden se mantiene: una activa vigente con orden menor gana al borrador.
    const activa = r({ id: 'a', orden: 1, vigente_desde: new Date('2025-01-01') });
    const borrador = r({ id: 'b', orden: 2, estado: 'BORRADOR', ...vig });
    expect(elegirRegla([borrador, activa], fecha, { MONEDA: 'USD' }, { incluirBorradores: true })?.id).toBe('a');
  });
});

describe('validarCondiciones', () => {
  it('rechaza vacío, dimensión desconocida, operador inválido y valores vacíos', () => {
    expect(() => validarCondiciones([])).toThrow(/al menos una condición/);
    expect(() => validarCondiciones([{ dimension: 'COLOR', operador: 'EN', valores: ['x'] }])).toThrow(/dimensión/);
    expect(() => validarCondiciones([{ dimension: 'MONEDA', operador: 'ES', valores: ['x'] }])).toThrow(/operador/);
    expect(() => validarCondiciones([{ dimension: 'MONEDA', operador: 'EN', valores: [] }])).toThrow(/valor/);
  });

  it('[I2] las dimensiones de ítem solo valen en los conceptos que se abren por ítem', () => {
    expect([...CONCEPTOS_CON_APERTURA_ITEM].sort()).toEqual(
      ['IMPORTACION_COMPRA', 'INM_VENTAS_5', 'INM_VENTAS_EXENTAS', 'INVENTARIO', 'VENTAS_10', 'VENTAS_5', 'VENTAS_EXENTAS'],
    );
    for (const dimension of ['CATEGORIA_PRODUCTO', 'RUBRO', 'TIPO_ITEM']) {
      const c = [{ dimension: 'MONEDA', operador: 'EN', valores: ['USD'] }, { dimension, operador: 'EN', valores: ['x'] }];
      expect(() => validarCondiciones(c, 'CLIENTES')).toThrow(/Condición 2: .*solo vale.*CLIENTES/);
      expect(() => validarCondiciones(c, 'PROVEEDORES')).toThrow(BadRequestException);
      expect(validarCondiciones(c, 'VENTAS_10')).toHaveLength(2);
      expect(validarCondiciones(c, 'INM_VENTAS_5')).toHaveLength(2);
      expect(validarCondiciones(c, 'INVENTARIO')).toHaveLength(2);
    }
    // Las de cabecera valen en cualquier concepto; sin concepto no se revisa (compatibilidad).
    expect(validarCondiciones([{ dimension: 'MONEDA', operador: 'EN', valores: ['USD'] }], 'CLIENTES')).toHaveLength(1);
    expect(validarCondiciones([{ dimension: 'RUBRO', operador: 'EN', valores: ['x'] }])).toHaveLength(1);
  });
});

it('usaDimensionesDeItem', () => {
  expect(usaDimensionesDeItem(r({}))).toBe(false);
  expect(usaDimensionesDeItem(r({ condiciones: [{ dimension: 'RUBRO', operador: 'EN', valores: ['x'] }] }))).toBe(true);
});

it('fraseRegla', () => {
  expect(fraseRegla(r({}), (_d, v) => v, '4.1.1.02 Ventas exterior')).toBe('Si moneda es USD → 4.1.1.02 Ventas exterior');
});

it('las dimensiones de ítem solo las publican ventas, NC de venta, compras y NC de compra', () => {
  for (const [origen, dims] of Object.entries(DIMENSIONES_POR_ORIGEN)) {
    const tieneItem = dims.some((d) => ['CATEGORIA_PRODUCTO', 'RUBRO', 'TIPO_ITEM'].includes(d));
    expect(tieneItem).toBe(['factura_cab', 'nota_credito_cab', 'compra_cab', 'nota_credito_compra_cab'].includes(origen));
  }
});

it('desempata por id cuando dos reglas tienen el mismo orden', () => {
  const a = r({ id: 'b', orden: 1, modo: 'CUENTA', cuenta_id: 'x' });
  const b = r({ id: 'a', orden: 1, modo: 'CUENTA', cuenta_id: 'y' });
  expect(elegirRegla([a, b], new Date('2026-10-13'), { MONEDA: 'USD' })?.id).toBe('a');
  expect(elegirRegla([b, a], new Date('2026-10-13'), { MONEDA: 'USD' })?.id).toBe('a');
});

it('fraseRegla con NO_EN: un valor "no es A", varios "no es A ni B"', () => {
  const uno = r({ condiciones: [{ dimension: 'CATEGORIA_PRODUCTO', operador: 'NO_EN', valores: ['A'] }] });
  const dos = r({ condiciones: [{ dimension: 'CATEGORIA_PRODUCTO', operador: 'NO_EN', valores: ['A', 'B'] }] });
  expect(fraseRegla(uno, (_d, v) => v, 'X')).toBe('Si categoría no es A → X');
  expect(fraseRegla(dos, (_d, v) => v, 'X')).toBe('Si categoría no es A ni B → X');
});

it('la NC de venta no publica CANAL ni el pago a proveedor SUCURSAL (no tienen ese dato); la NC de compra publica la SUCURSAL de su compra', () => {
  expect(DIMENSIONES_POR_ORIGEN.factura_cab).toContain('CANAL');
  expect(DIMENSIONES_POR_ORIGEN.nota_credito_cab).not.toContain('CANAL');
  expect(DIMENSIONES_POR_ORIGEN.compra_cab).toContain('SUCURSAL');
  expect(DIMENSIONES_POR_ORIGEN.nota_credito_compra_cab).toContain('SUCURSAL');
  // pagos_proveedor no tiene sucursal_id; la orden de pago sí.
  expect(DIMENSIONES_POR_ORIGEN.pagos_proveedor).not.toContain('SUCURSAL');
  expect(DIMENSIONES_POR_ORIGEN.orden_pago_proveedor_cab).toContain('SUCURSAL');
});
