import { ConsumoFuncionarioConfigService } from './consumo-funcionario-config.service';
import { EmpleadoClienteService } from './empleado-cliente.service';

const EMPRESA = 'emp-1';

function crear(
  opts: {
    concepto?: unknown;
    mapeo?: unknown;
    cuenta?: unknown;
    empleadosConEseCliente?: number;
    empleadosSinFicha?: Array<{ id: string; cedula_identidad: string }>;
  } = {},
) {
  const prisma = {
    config_rrhh: {
      findUnique: jest.fn().mockResolvedValue(null),
      upsert: jest.fn().mockResolvedValue({ id: 'cfg-1' }),
    },
    rrhh_conceptos_liquidacion: {
      findFirst: jest.fn().mockResolvedValue(opts.concepto ?? null),
      create: jest.fn().mockResolvedValue({ id: 'c-1' }),
    },
    cont_mapeo_cuentas: { findUnique: jest.fn().mockResolvedValue(opts.mapeo ?? null) },
    cuentas_cobrar: {
      findFirst: jest.fn().mockResolvedValue(opts.cuenta ?? null),
      update: jest.fn().mockResolvedValue({}),
    },
    rrhh_empleados: {
      count: jest.fn().mockResolvedValue(opts.empleadosConEseCliente ?? 0),
      findMany: jest.fn().mockResolvedValue(opts.empleadosSinFicha ?? []),
    },
  };
  const audit = { log: jest.fn() };
  const empleadoCliente = { resolverParaEmpleado: jest.fn().mockResolvedValue('cli-1') };
  const service = new ConsumoFuncionarioConfigService(
    prisma as never,
    audit as never,
    empleadoCliente as unknown as EmpleadoClienteService,
  );
  return { service, prisma, audit, empleadoCliente };
}

describe('ConsumoFuncionarioConfigService', () => {
  it('al activarlo crea el concepto CONSUMO_FUNCIONARIO si la empresa no lo tiene', async () => {
    const { service, prisma } = crear();
    await service.actualizar(EMPRESA, { habilitado: true }, 'u-1');

    expect(prisma.config_rrhh.upsert).toHaveBeenCalledWith(
      expect.objectContaining({ update: expect.objectContaining({ consumo_funcionario_habilitado: true }) }),
    );
    expect(prisma.rrhh_conceptos_liquidacion.create).toHaveBeenCalledWith({
      data: expect.objectContaining({ empresa_id: EMPRESA, codigo: 'CONSUMO_FUNCIONARIO', tipo: 'EGRESO', es_del_sistema: true }),
    });
  });

  it('no duplica el concepto si ya existe', async () => {
    const { service, prisma } = crear({ concepto: { id: 'c-0', activo: true } });
    await service.actualizar(EMPRESA, { habilitado: true });
    expect(prisma.rrhh_conceptos_liquidacion.create).not.toHaveBeenCalled();
  });

  it('desactivar o cambiar solo el tope no crea el concepto ni toca los otros campos', async () => {
    const { service, prisma } = crear();
    await service.actualizar(EMPRESA, { tope_mensual: 300000 });
    expect(prisma.rrhh_conceptos_liquidacion.create).not.toHaveBeenCalled();
    expect(prisma.config_rrhh.upsert.mock.calls[0][0].update).toEqual(
      expect.objectContaining({ consumo_funcionario_tope_mensual: 300000 }),
    );
    expect(prisma.config_rrhh.upsert.mock.calls[0][0].update).not.toHaveProperty('consumo_funcionario_habilitado');
  });

  it.each([
    { caso: 'sin mapeo', mapeo: null, ok: false, problema: 'SIN_MAPEO' },
    { caso: 'cuenta inactiva', mapeo: { cuenta: { codigo: '1.1', descripcion: 'x', active: false, acepta_movimientos: true } }, ok: false, problema: 'CUENTA_INACTIVA' },
    { caso: 'cuenta de agrupación', mapeo: { cuenta: { codigo: '1.1', descripcion: 'x', active: true, acepta_movimientos: false } }, ok: false, problema: 'CUENTA_NO_IMPUTABLE' },
    { caso: 'cuenta imputable', mapeo: { cuenta: { codigo: '1.1.2.06', descripcion: 'x', active: true, acepta_movimientos: true } }, ok: true, problema: null },
  ])('cuenta puente: $caso → ok=$ok', async ({ mapeo, ok, problema }) => {
    const { service } = crear({ mapeo });
    const r = await service.obtener(EMPRESA);
    expect(r.requisitos.cuenta_puente).toEqual(expect.objectContaining({ ok, problema }));
  });

  it('vincular fichas saltea cédulas de prueba y vacías, y respeta "crear"', async () => {
    const { service, empleadoCliente } = crear({
      empleadosSinFicha: [
        { id: 'e-1', cedula_identidad: '5.616.813' },
        { id: 'e-2', cedula_identidad: '123456' },
        { id: 'e-3', cedula_identidad: '' },
      ],
    });
    const r = await service.vincularFichas(EMPRESA, false, 'u-1');

    expect(empleadoCliente.resolverParaEmpleado).toHaveBeenCalledTimes(1);
    expect(empleadoCliente.resolverParaEmpleado).toHaveBeenCalledWith('e-1', EMPRESA, false);
    expect(r).toEqual(expect.objectContaining({ vinculados: 1, salteados: 2 }));
  });

  it('excluir una factura de funcionario la marca y lo audita', async () => {
    const { service, prisma, audit } = crear({
      cuenta: { id: 'cc-1', cliente_id: 'cli-1', factura_cab: { dnumdoc: '0002256' } },
      empleadosConEseCliente: 1,
    });
    await service.cambiarExclusion(EMPRESA, 'cc-1', true, 'u-1');

    expect(prisma.cuentas_cobrar.update).toHaveBeenCalledWith({
      where: { id: 'cc-1' },
      data: expect.objectContaining({ excluir_descuento_planilla: true }),
    });
    expect(audit.log).toHaveBeenCalledWith(expect.objectContaining({ entity_type: 'cuentas_cobrar', entity_id: 'cc-1' }));
  });

  it('no deja marcar la cuenta por cobrar de un cliente que no es funcionario', async () => {
    const { service, prisma } = crear({
      cuenta: { id: 'cc-9', cliente_id: 'cli-externo', factura_cab: null },
      empleadosConEseCliente: 0,
    });
    await expect(service.cambiarExclusion(EMPRESA, 'cc-9', true)).rejects.toThrow('Factura de funcionario no encontrada');
    expect(prisma.cuentas_cobrar.update).not.toHaveBeenCalled();
  });
});
