import { explicarPendiente } from './causa-pendiente';
import { ContabilidadConciliacionService } from './contabilidad-conciliacion.service';

// Fechas locales: el texto se muestra con la fecha del calendario del usuario.
const d = (s: string) => { const [y, m, dd] = s.split('-').map(Number); return new Date(y, m - 1, dd); };

describe('explicarPendiente', () => {
  it('registro creado antes de activar Contabilidad', () => {
    const r = explicarPendiente({ creadoEn: d('2026-03-01'), activacionContabilidad: d('2026-06-06'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null });
    expect(r.codigo).toBe('ANTERIOR_A_CONTABILIDAD');
    expect(r.texto).toContain('06/06/2026');
  });

  it('la categoría o concepto se mapeó después de crear el registro', () => {
    const r = explicarPendiente({
      creadoEn: d('2026-06-20'), activacionContabilidad: d('2026-06-06'), origenTipo: null,
      mapeosPosteriores: [{ nombre: 'Cheque Depositado', fecha: d('2026-07-09') }], ultimoIntento: null,
    });
    expect(r.codigo).toBe('MAPEO_POSTERIOR');
    expect(r.texto).toContain('Cheque Depositado');
    expect(r.texto).toContain('09/07/2026');
  });

  it('documento importado de Marangatu o del sistema anterior', () => {
    const r = explicarPendiente({ creadoEn: d('2026-07-01'), activacionContabilidad: d('2026-06-06'), origenTipo: null, importadoDesde: 'Marangatu', mapeosPosteriores: [], ultimoIntento: null });
    expect(r.codigo).toBe('IMPORTADO');
    expect(r.texto).toContain('Marangatu');
  });

  it('importado desde extracto bancario', () => {
    const r = explicarPendiente({ creadoEn: d('2026-07-01'), activacionContabilidad: d('2026-06-06'), origenTipo: 'IMPORT_EXTRACTO', mapeosPosteriores: [], ultimoIntento: null });
    expect(r.codigo).toBe('IMPORTACION_EXTRACTO');
  });

  it('el último intento de generarlo falló, con su error', () => {
    const r = explicarPendiente({
      creadoEn: d('2026-07-01'), activacionContabilidad: d('2026-06-06'), origenTipo: null, mapeosPosteriores: [],
      ultimoIntento: { fecha: d('2026-08-01'), error: 'No hay período abierto' },
    });
    expect(r.codigo).toBe('INTENTO_FALLIDO');
    expect(r.texto).toContain('01/08/2026');
    expect(r.texto).toContain('No hay período abierto');
  });

  it('integración auditada como OK pero sin asiento (carrera con la transacción)', () => {
    const r = explicarPendiente({ creadoEn: d('2026-07-01'), activacionContabilidad: d('2026-06-06'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null, integracionMarcadaOk: d('2026-07-01') });
    expect(r.codigo).toBe('INTEGRACION_SILENCIOSA');
    expect(r.texto).toContain('01/07/2026');
  });

  it('sin evidencia, lo dice', () => {
    const r = explicarPendiente({ creadoEn: d('2026-07-01'), activacionContabilidad: d('2026-06-06'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null });
    expect(r.codigo).toBe('SIN_REGISTRO');
  });

  it('la activación manda sobre todo lo demás', () => {
    const r = explicarPendiente({
      creadoEn: d('2026-01-01'), activacionContabilidad: d('2026-06-06'), origenTipo: 'IMPORT_EXTRACTO',
      mapeosPosteriores: [{ nombre: 'X', fecha: d('2026-07-09') }], ultimoIntento: { fecha: d('2026-08-01'), error: 'x' },
    });
    expect(r.codigo).toBe('ANTERIOR_A_CONTABILIDAD');
  });
  it('falta de mapeo gana sobre un intento fallido y dice qué mapear', () => {
    const r = explicarPendiente({
      creadoEn: new Date('2026-10-10'), activacionContabilidad: new Date('2026-01-01'), origenTipo: null,
      mapeosPosteriores: [], ultimoIntento: { fecha: new Date('2026-10-11'), error: 'otro' },
      faltaMapeo: { conceptos: ['IMPORTACION_COMPRA'], recomendaciones: { IMPORTACION_COMPRA: '1.01.04.11 IMPORTACIONES EN CURSO' } },
    });
    expect(r.codigo).toBe('FALTA_MAPEO');
    expect(r.texto).toMatch(/IMPORTACION_COMPRA/);
    expect(r.texto).toMatch(/1\.01\.04\.11/);
    expect(r.conceptos).toEqual(['IMPORTACION_COMPRA']);
  });

  it('[I1] una regla con cuenta que no sirve: la causa nombra la regla y dice retirarla y duplicarla', () => {
    const r = explicarPendiente({
      creadoEn: d('2026-10-10'), activacionContabilidad: d('2026-01-01'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null,
      faltaMapeo: { conceptos: ['VENTAS_10', 'CLIENTES'], recomendaciones: {}, reglas: { VENTAS_10: [2] } },
    });
    expect(r.codigo).toBe('FALTA_MAPEO');
    expect(r.texto).toMatch(/la regla n\.º 2 de VENTAS_10 apunta a una cuenta que no sirve/);
    expect(r.texto).toMatch(/retirala y duplicala con otra cuenta/);
    expect(r.texto).toMatch(/falta mapear CLIENTES/);
    expect(r.conceptos).toEqual(['VENTAS_10', 'CLIENTES']);
  });

  it('falta de mapeo sin recomendación nombra solo el concepto', () => {
    const r = explicarPendiente({
      creadoEn: d('2026-10-10'), activacionContabilidad: d('2026-01-01'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null,
      faltaMapeo: { conceptos: ['INVENTARIO'], recomendaciones: {} },
    });
    expect(r.codigo).toBe('FALTA_MAPEO');
    expect(r.texto).toContain('falta mapear INVENTARIO.');
  });

  it('medio de pago con cuenta que no sirve: la causa es el detalle (medio y cuenta), no "falta mapear"', () => {
    const detalle = 'la cuenta 1.01.02 del medio Transferencia (al cobrar) está desactivada; elegí otra en Contabilidad › Mapeo de cuentas › Cuentas por medio de pago';
    const r = explicarPendiente({
      creadoEn: d('2026-10-10'), activacionContabilidad: d('2026-01-01'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null,
      faltaMapeo: { conceptos: ['MEDIO_PAGO_COBRO'], recomendaciones: {}, detalles: { MEDIO_PAGO_COBRO: detalle } },
    });
    expect(r.codigo).toBe('FALTA_MAPEO');
    expect(r.texto).toContain(`No se contabilizó porque ${detalle}.`);
    expect(r.texto).not.toMatch(/falta mapear/);
    expect(r.texto).toMatch(/regenerá desde acá/);
  });

  it('cierre de importación sin saldo en la puente: no hay nada que trasladar', () => {
    const r = explicarPendiente({
      creadoEn: d('2026-10-10'), activacionContabilidad: d('2026-01-01'), origenTipo: null, mapeosPosteriores: [], ultimoIntento: null,
      sinSaldoPuente: true,
      faltaMapeo: { conceptos: ['INVENTARIO'], recomendaciones: {} },
    });
    expect(r.codigo).toBe('SIN_SALDO_PUENTE');
    expect(r.texto).toBe(
      'La cuenta puente no recibió costos de esta importación por documentos del sistema: no hay nada que trasladar. Si el costo se registró con un asiento manual, el cierre también va manual.',
    );
  });
});

describe('Conciliación: conceptos que siguen faltando por documento', () => {
  const crear = (constancias: any[], mapeos: any[], reglas: any[] = [], mapeosMedio: any[] = []) => {
    const s = Object.create(ContabilidadConciliacionService.prototype) as any;
    s.prisma = {
      audit_logs: { findMany: jest.fn().mockResolvedValue(constancias) },
      cont_mapeo_cuentas: { findMany: jest.fn().mockResolvedValue(mapeos) },
      cont_mapeo_reglas: { findMany: jest.fn().mockResolvedValue(reglas) },
      cont_mapeo_medio_pago: { findMany: jest.fn().mockResolvedValue(mapeosMedio) },
    };
    return s;
  };
  const puente = { codigo: '1.01.04.11', descripcion: 'IMPORTACIONES EN CURSO', active: true, acepta_movimientos: true };

  it('usa la última constancia, descarta lo ya mapeado y resuelve la recomendación', async () => {
    const s = crear(
      [
        { entity_id: 'c1', new_value: { origen_tipo: 'compra_cab', conceptos: ['IMPORTACION_COMPRA', 'PROVEEDORES'] } },
        { entity_id: 'c1', new_value: { origen_tipo: 'compra_cab', conceptos: ['VIEJO'] } },
      ],
      [
        { concepto: 'IMPORTACIONES_EN_CURSO', modo: 'CUENTA', cuenta: puente },
        { concepto: 'PROVEEDORES', modo: 'CUENTA', cuenta: { ...puente, codigo: '2.01' } },
      ],
    );
    const r = await s.faltaMapeoPorId('emp', 'compra_cab', ['c1']);
    expect(s.prisma.audit_logs.findMany.mock.calls[0][0].where).toMatchObject({ action: 'CONT_FALTA_MAPEO', entity_type: 'compra_cab' });
    expect(r.get('c1')).toEqual({
      conceptos: ['IMPORTACION_COMPRA'],
      recomendaciones: { IMPORTACION_COMPRA: expect.stringContaining('1.01.04.11 IMPORTACIONES EN CURSO') },
    });
  });

  it('[I1] defecto usable pero la regla de la constancia apunta a una cuenta que no sirve: sigue faltando, con la regla', async () => {
    const regla = { id: 'r9', orden: 2, concepto: 'VENTAS_10' };
    const rota = { id: 'r9', concepto: 'VENTAS_10', orden: 2, estado: 'ACTIVA', modo: 'CUENTA', cuenta_id: 'g', vigente_desde: new Date('2026-01-01'), vigente_hasta: null,
      cuenta: { active: true, acepta_movimientos: false, empresa_id: 'emp' } };
    const s = crear(
      [{ entity_id: 'f1', new_value: { origen_tipo: 'factura_cab', conceptos: ['VENTAS_10'], motivo: 'CUENTA_INUTILIZABLE', regla } }],
      [{ concepto: 'VENTAS_10', modo: 'CUENTA', cuenta: { ...puente, codigo: '4.1.1.01' } }],
      [rota],
    );
    const r = await s.faltaMapeoPorId('emp', 'factura_cab', ['f1']);
    expect(r.get('f1')).toMatchObject({ conceptos: ['VENTAS_10'], reglas: { VENTAS_10: [2] } });
    // Retirada sin vigencia (nunca aplicó): el documento ya se puede regenerar con el defecto.
    const s2 = crear(
      [{ entity_id: 'f1', new_value: { origen_tipo: 'factura_cab', conceptos: ['VENTAS_10'], motivo: 'CUENTA_INUTILIZABLE', regla } }],
      [{ concepto: 'VENTAS_10', modo: 'CUENTA', cuenta: { ...puente, codigo: '4.1.1.01' } }],
      [{ ...rota, estado: 'RETIRADA', vigente_desde: new Date('2026-10-14'), vigente_hasta: new Date('2026-10-14') }],
    );
    expect((await s2.faltaMapeoPorId('emp', 'factura_cab', ['f1'])).get('f1').conceptos).toEqual([]);
  });

  it('un mapeo a una cuenta desactivada sigue faltando', async () => {
    const s = crear(
      [{ entity_id: 'g1', new_value: { origen_tipo: 'gasto_cab', conceptos: ['GASTOS_GENERALES'] } }],
      [{ concepto: 'GASTOS_GENERALES', modo: 'CUENTA', cuenta: { ...puente, active: false } }],
    );
    const r = await s.faltaMapeoPorId('emp', 'gasto_cab', ['g1']);
    expect(r.get('g1').conceptos).toEqual(['GASTOS_GENERALES']);
  });
  it('medio de pago con cuenta que no sirve: sigue faltando con su detalle hasta que se corrige el mapeo del medio', async () => {
    const detalle = 'la cuenta 1.01.02 del medio Transferencia (al cobrar) está desactivada; elegí otra en …';
    const constancias = [{ entity_id: 'f1', new_value: { origen_tipo: 'factura_cab', conceptos: ['MEDIO_PAGO_COBRO'], motivo: 'CUENTA_INUTILIZABLE', detalles: { MEDIO_PAGO_COBRO: detalle } } }];
    const roto = { medio_pago_id: 'mp', flujo: 'COBRO', cuenta_contable_id: 'c', medio_pago: { codigo: 5, descripcion: 'Transferencia' },
      cuenta: { codigo: '1.01.02', descripcion: 'Banco viejo', active: false, acepta_movimientos: true } };

    const r = await crear(constancias, [], [], [roto]).faltaMapeoPorId('emp', 'factura_cab', ['f1']);
    expect(r.get('f1')).toEqual({ conceptos: ['MEDIO_PAGO_COBRO'], recomendaciones: {}, detalles: { MEDIO_PAGO_COBRO: detalle } });

    const mixta = [{ entity_id: 'f1', new_value: { origen_tipo: 'factura_cab', conceptos: ['MEDIO_PAGO_COBRO', 'CLIENTES'], detalles: { MEDIO_PAGO_COBRO: detalle } } }];
    expect((await crear(mixta, [], [], [roto]).faltaMapeoPorId('emp', 'factura_cab', ['f1'])).get('f1')).toEqual({
      conceptos: ['MEDIO_PAGO_COBRO', 'CLIENTES'], recomendaciones: {}, detalles: { MEDIO_PAGO_COBRO: detalle },
    });

    const arreglado = await crear(constancias, [], [], [{ ...roto, cuenta: { ...roto.cuenta, active: true } }]).faltaMapeoPorId('emp', 'factura_cab', ['f1']);
    expect(arreglado.get('f1').conceptos).toEqual([]);
  });
});
