const hoisted = vi.hoisted(() => ({
  redisConnect: vi.fn(),
  redisQuit: vi.fn(),
  createAdapterMock: vi.fn(() => Symbol('adapter')),
}));

vi.mock('ioredis', () => {
  class FakeRedis {
    connect = hoisted.redisConnect;
    quit = hoisted.redisQuit;
    duplicate() {
      return new FakeRedis();
    }
  }
  return { default: FakeRedis };
});

vi.mock('@socket.io/redis-adapter', () => ({
  createAdapter: hoisted.createAdapterMock,
}));

import { RedisIoAdapter } from '../redis-io.adapter';

function createApp(): unknown {
  return {
    get: () => ({
      log: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
      debug: vi.fn(),
    }),
    getHttpServer: () => undefined,
  };
}

describe('RedisIoAdapter', () => {
  beforeEach(() => {
    hoisted.redisConnect.mockReset();
    hoisted.redisQuit.mockReset();
    hoisted.createAdapterMock.mockClear();
  });

  it('connects pub/sub clients and registers the adapter on the server', async () => {
    hoisted.redisConnect.mockResolvedValue(undefined);
    const adapter = new RedisIoAdapter(createApp() as any);

    await adapter.connectToRedis();

    expect(hoisted.redisConnect).toHaveBeenCalledTimes(2);
    expect(hoisted.createAdapterMock).toHaveBeenCalledTimes(1);

    const adapterFn = vi.fn();
    const fakeServer = { adapter: adapterFn };
    const superProto = Object.getPrototypeOf(Object.getPrototypeOf(adapter)) as object;
    vi.spyOn(superProto, 'createIOServer').mockReturnValue(fakeServer);

    const result = adapter.createIOServer(3001);

    expect(adapterFn).toHaveBeenCalledTimes(1);
    expect(result).toBe(fakeServer);
  });

  it('falls back silently when Redis pub/sub connect fails', async () => {
    hoisted.redisConnect.mockRejectedValue(new Error('connection refused'));
    const adapter = new RedisIoAdapter(createApp() as any);

    await adapter.connectToRedis();

    expect(hoisted.createAdapterMock).not.toHaveBeenCalled();

    const fakeServer = { adapter: vi.fn() };
    const superProto = Object.getPrototypeOf(Object.getPrototypeOf(adapter)) as object;
    vi.spyOn(superProto, 'createIOServer').mockReturnValue(fakeServer);

    adapter.createIOServer(3001);

    expect(fakeServer.adapter).not.toHaveBeenCalled();
  });

  it('close() quits pub/sub clients', async () => {
    hoisted.redisConnect.mockResolvedValue(undefined);
    hoisted.redisQuit.mockResolvedValue(undefined);
    const adapter = new RedisIoAdapter(createApp() as any);
    await adapter.connectToRedis();

    await adapter.close();

    expect(hoisted.redisQuit).toHaveBeenCalledTimes(2);
  });
});
