import { BadRequestException } from '@nestjs/common';
import { LiquidacionesService } from './liquidaciones.service';

describe('LiquidacionesService - límites legales de cierre RRHH', () => {
  const createService = () => {
    const prisma = {
      empresas: {
        findUnique: jest.fn(),
      },
      usuario: {
        findFirst: jest.fn(),
      },
      rrhh_parametros_sistema: {
        findFirst: jest.fn(),
      },
      rrhh_liquidaciones_detalle: {
        findMany: jest.fn(),
      },
      rrhh_liquidaciones_cabecera: {
        findFirst: jest.fn(),
      },
      rrhh_liq_empleado_resumen: {
        findMany: jest.fn(),
        aggregate: jest.fn(),
      },
    };
    const engine = {} as any;
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    const contabilidadIntegracion = {
      integrarLiquidacionRrhh: jest.fn().mockResolvedValue(undefined),
      findAsientoPorLiquidacion: jest.fn().mockResolvedValue(null),
    };
    const service = new LiquidacionesService(
      prisma as any,
      engine,
      auditService as any,
      contabilidadIntegracion as any,
    );
    return { service, prisma, auditService, contabilidadIntegracion };
  };

  it('bloquea cierre cuando descuentos no-embargo superan 30% del ingreso', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_parametros_sistema.findFirst.mockImplementation(({ where }: any) => {
      if (where.clave === 'LIMITE_DESCUENTO_SALARIO') return { valor: '0.30' };
      if (where.clave === 'LIMITE_EMBARGO_JUDICIAL') return { valor: '0.50' };
      return null;
    });
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([
      {
        empleado_id: 'emp-1',
        monto: 90,
        concepto: { codigo: 'IPS_OBRERO', nombre: 'IPS obrero', categoria: 'IPS' },
      },
      {
        empleado_id: 'emp-1',
        monto: 250,
        concepto: { codigo: 'ANTICIPO_QUINCENAL', nombre: 'Anticipo', categoria: 'ANTICIPO' },
      },
      {
        empleado_id: 'emp-1',
        monto: 100,
        concepto: { codigo: 'DESCUENTO_AUSENCIA', nombre: 'Ausencia', categoria: 'DESCUENTO' },
      },
    ]);

    await expect(
      (service as any).validarLimitesDescuentoEmbargo({
        liquidacionId: 'liq-1',
        empresaId: 'empresa-1',
        periodoAnio: 2026,
        periodoMes: 5,
        resumenes: [{ empleado_id: 'emp-1', total_ingresos: 1000, empleado: { numero_empleado: 'E-001' } }],
      }),
    ).rejects.toThrow(BadRequestException);
  });

  it('bloquea cierre cuando embargos superan 50% del ingreso', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_parametros_sistema.findFirst.mockImplementation(({ where }: any) => {
      if (where.clave === 'LIMITE_DESCUENTO_SALARIO') return { valor: '0.30' };
      if (where.clave === 'LIMITE_EMBARGO_JUDICIAL') return { valor: '0.50' };
      return null;
    });
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([
      {
        empleado_id: 'emp-1',
        monto: 600,
        concepto: { codigo: 'EMBARGO_JUDICIAL', nombre: 'Embargo judicial', categoria: 'DESCUENTO' },
      },
    ]);

    await expect(
      (service as any).validarLimitesDescuentoEmbargo({
        liquidacionId: 'liq-1',
        empresaId: 'empresa-1',
        periodoAnio: 2026,
        periodoMes: 5,
        resumenes: [{ empleado_id: 'emp-1', total_ingresos: 1000, empleado: { numero_empleado: 'E-001' } }],
      }),
    ).rejects.toThrow(BadRequestException);
  });

  it('permite cierre cuando descuentos y embargos están dentro de límites', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_parametros_sistema.findFirst.mockImplementation(({ where }: any) => {
      if (where.clave === 'LIMITE_DESCUENTO_SALARIO') return { valor: '0.30' };
      if (where.clave === 'LIMITE_EMBARGO_JUDICIAL') return { valor: '0.50' };
      return null;
    });
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([
      {
        empleado_id: 'emp-1',
        monto: 90,
        concepto: { codigo: 'IPS_OBRERO', nombre: 'IPS obrero', categoria: 'IPS' },
      },
      {
        empleado_id: 'emp-1',
        monto: 250,
        concepto: { codigo: 'ANTICIPO_QUINCENAL', nombre: 'Anticipo', categoria: 'ANTICIPO' },
      },
      {
        empleado_id: 'emp-1',
        monto: 200,
        concepto: { codigo: 'EMBARGO_JUDICIAL', nombre: 'Embargo judicial', categoria: 'DESCUENTO' },
      },
    ]);

    await expect(
      (service as any).validarLimitesDescuentoEmbargo({
        liquidacionId: 'liq-1',
        empresaId: 'empresa-1',
        periodoAnio: 2026,
        periodoMes: 5,
        resumenes: [{ empleado_id: 'emp-1', total_ingresos: 1000, empleado: { numero_empleado: 'E-001' } }],
      }),
    ).resolves.toBeUndefined();
  });

  it('bloquea cierre cuando la liquidación está pendiente de recálculo', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_liquidaciones_cabecera.findFirst.mockResolvedValue({
      id: 'liq-1',
      empresa_id: 'empresa-1',
      estado: 'PRE_LIQUIDACION',
      tipo: 'MENSUAL',
      periodo_anio: 2026,
      periodo_mes: 5,
      sucursal_id: null,
      recalculo_pendiente: true,
    });

    await expect(
      service.cerrar('liq-1', 'empresa-1', { comentario: 'cierre' }, 'user-1'),
    ).rejects.toThrow(/requiere recálculo/i);
  });

  it('bloquea cierre con neto negativo cuando el parámetro de excepción está deshabilitado', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_liquidaciones_cabecera.findFirst
      .mockResolvedValueOnce({
        id: 'liq-1',
        empresa_id: 'empresa-1',
        estado: 'PRE_LIQUIDACION',
        tipo: 'MENSUAL',
        periodo_anio: 2026,
        periodo_mes: 5,
        sucursal_id: null,
      })
      .mockResolvedValueOnce(null);
    prisma.rrhh_liq_empleado_resumen.findMany.mockResolvedValue([
      {
        empleado_id: 'emp-1',
        total_ingresos: 1000,
        total_egresos: 1200,
        neto_a_pagar: -200,
        empleado: { numero_empleado: 'E-001' },
      },
    ]);
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([]);
    prisma.rrhh_parametros_sistema.findFirst.mockImplementation(({ where }: any) => {
      if (where.clave === 'LIMITE_DESCUENTO_SALARIO') return { valor: '0.30' };
      if (where.clave === 'LIMITE_EMBARGO_JUDICIAL') return { valor: '0.50' };
      if (where.clave === 'PERMITIR_NETO_NEGATIVO_EXCEPCION') return { valor: 'false' };
      return null;
    });

    await expect(
      service.cerrar('liq-1', 'empresa-1', { comentario: 'cierre' }, 'user-1'),
    ).rejects.toThrow(BadRequestException);
  });

  it('bloquea cierre con neto negativo si falta confirmación/motivo aunque el parámetro esté habilitado', async () => {
    const { service, prisma, auditService } = createService();
    prisma.rrhh_liquidaciones_cabecera.findFirst
      .mockResolvedValueOnce({
        id: 'liq-1',
        empresa_id: 'empresa-1',
        estado: 'PRE_LIQUIDACION',
        tipo: 'MENSUAL',
        periodo_anio: 2026,
        periodo_mes: 5,
        sucursal_id: null,
      })
      .mockResolvedValueOnce(null);
    prisma.rrhh_liq_empleado_resumen.findMany.mockResolvedValue([
      {
        empleado_id: 'emp-1',
        total_ingresos: 1000,
        total_egresos: 1200,
        neto_a_pagar: -200,
        empleado: { numero_empleado: 'E-001' },
      },
    ]);
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([]);
    prisma.rrhh_parametros_sistema.findFirst.mockImplementation(({ where }: any) => {
      if (where.clave === 'LIMITE_DESCUENTO_SALARIO') return { valor: '0.30' };
      if (where.clave === 'LIMITE_EMBARGO_JUDICIAL') return { valor: '0.50' };
      if (where.clave === 'PERMITIR_NETO_NEGATIVO_EXCEPCION') return { valor: 'true' };
      return null;
    });

    await expect(
      service.cerrar(
        'liq-1',
        'empresa-1',
        {
          comentario: 'cierre',
          confirmar_excepcion_neto_negativo: true,
          motivo_excepcion_neto_negativo: 'corto',
        },
        'user-1',
      ),
    ).rejects.toThrow(BadRequestException);
    expect(auditService.log).not.toHaveBeenCalled();
  });

  it('obtiene planilla consolidada json para liquidación mensual cerrada', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_liquidaciones_cabecera.findFirst.mockResolvedValue({
      id: 'liq-1',
      empresa_id: 'empresa-1',
      tipo: 'MENSUAL',
      estado: 'CERRADA',
      periodo_anio: 2026,
      periodo_mes: 5,
      sucursal_id: 'suc-1',
      sucursal: { id: 'suc-1', descripcion: 'Matriz' },
      fecha_cierre: new Date('2026-05-31T12:00:00Z'),
      cerrado_por: 'usr-1',
    });
    prisma.empresas.findUnique.mockResolvedValue({
      id: 'empresa-1',
      razon_social: 'Empresa Demo',
      ruc: '80012345-6',
    });
    prisma.usuario.findFirst.mockResolvedValue({
      id: 'usr-1',
      nombres: 'Juan',
      apellidos: 'Perez',
      username: 'jperez',
      email: 'jperez@demo.com',
    });
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([
      {
        concepto_id: 'c-1',
        empleado_id: 'e-1',
        monto: 1000000,
        tipo: 'INGRESO',
        origen: 'AUTOMATICO',
        empleado: {
          id: 'e-1',
          numero_empleado: 'EMP-001',
          nombres: 'Ana',
          apellidos: 'Gomez',
          cedula_identidad: '1111111',
        },
        concepto: { id: 'c-1', codigo: 'SALARIO_BASE', nombre: 'Salario base', tipo: 'INGRESO' },
        centro_costo: { codigo: 'ADM', nombre: 'Administración' },
      },
      {
        concepto_id: 'c-2',
        empleado_id: 'e-1',
        monto: 90000,
        tipo: 'EGRESO',
        origen: 'AUTOMATICO',
        empleado: {
          id: 'e-1',
          numero_empleado: 'EMP-001',
          nombres: 'Ana',
          apellidos: 'Gomez',
          cedula_identidad: '1111111',
        },
        concepto: { id: 'c-2', codigo: 'IPS_OBRERO', nombre: 'IPS obrero', tipo: 'EGRESO' },
        centro_costo: { codigo: 'ADM', nombre: 'Administración' },
      },
    ]);
    prisma.rrhh_liq_empleado_resumen.aggregate.mockResolvedValue({
      _sum: { total_ingresos: 1000000, total_egresos: 90000, neto_a_pagar: 910000 },
    });

    const out = await service.obtenerPlanillaConsolidada('liq-1', 'empresa-1');
    expect(out.resumen_conceptos).toHaveLength(2);
    expect(out.detalle).toHaveLength(2);
    expect(out.totales).toEqual({ ingresos: 1000000, egresos: 90000, neto: 910000 });
    expect(out.metadata.usuario_cierre?.nombre).toBe('Juan Perez');
  });

  it('rechaza planilla cuando la liquidación no es mensual cerrada', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_liquidaciones_cabecera.findFirst.mockResolvedValue({
      id: 'liq-2',
      empresa_id: 'empresa-1',
      tipo: 'VACACIONES',
      estado: 'CERRADA',
      periodo_anio: 2026,
      periodo_mes: 5,
      sucursal_id: null,
      sucursal: null,
      fecha_cierre: new Date('2026-05-31T12:00:00Z'),
      cerrado_por: null,
    });

    await expect(service.obtenerPlanillaConsolidada('liq-2', 'empresa-1')).rejects.toThrow(
      'MENSUALES',
    );
  });

  it('genera xlsx con dos hojas para planilla consolidada', async () => {
    const { service, prisma } = createService();
    prisma.rrhh_liquidaciones_cabecera.findFirst.mockResolvedValue({
      id: 'liq-3',
      empresa_id: 'empresa-1',
      tipo: 'MENSUAL',
      estado: 'CERRADA',
      periodo_anio: 2026,
      periodo_mes: 4,
      sucursal_id: null,
      sucursal: null,
      fecha_cierre: new Date('2026-04-30T12:00:00Z'),
      cerrado_por: null,
    });
    prisma.empresas.findUnique.mockResolvedValue({
      id: 'empresa-1',
      razon_social: 'Empresa Demo',
      ruc: '80012345-6',
    });
    prisma.usuario.findFirst.mockResolvedValue(null);
    prisma.rrhh_liquidaciones_detalle.findMany.mockResolvedValue([]);
    prisma.rrhh_liq_empleado_resumen.aggregate.mockResolvedValue({
      _sum: { total_ingresos: 0, total_egresos: 0, neto_a_pagar: 0 },
    });

    const out = await service.generarPlanillaConsolidadaXlsx('liq-3', 'empresa-1');
    expect(Buffer.isBuffer(out.buffer)).toBe(true);
    expect(out.buffer.length).toBeGreaterThan(0);
    expect(out.filename).toContain('planilla_rrhh_2026-04');
  });
});
