import { WebSocket, Server } from 'mock-socket';
import { KioskConnection } from '../kiosk/KioskConnection';
import { kioskSDK } from '../kiosk/KioskSDK';

// Mock the ws module
jest.mock('ws', () => ({
  WebSocket: WebSocket
}));

describe('KioskConnection', () => {
  let mockServer: Server;
  let connection: KioskConnection;
  const TEST_URL = 'ws://localhost:8080';

  beforeEach(() => {
    mockServer = new Server(TEST_URL);
  });

  afterEach(async () => {
    // Ensure proper cleanup in order
    if (connection) {
      await connection.disconnect();
    }
    mockServer.stop();
    jest.clearAllTimers();
  });

  it('should emit connected event on successful connection', (done) => {
    connection = new KioskConnection({
      url: TEST_URL,
      kioskId: 'test-kiosk'
    });

    connection.on('connected', () => {
      try {
        expect(connection.isConnectionOpen()).toBe(true);
        done();
      } catch (error) {
        done(error);
      }
    });

    connection.connect();
  });

  it('should emit disconnected event when closing connection', async () => {
    connection = new KioskConnection({
      url: TEST_URL,
      kioskId: 'test-kiosk'
    });

    // Create a promise that resolves on connection
    const connectionPromise = new Promise<void>((resolve) => {
      connection.on('connected', resolve);
    });

    // Create a promise that resolves on disconnection
    const disconnectionPromise = new Promise<void>((resolve) => {
      connection.on('disconnected', resolve);
    });

    // Start connection
    connection.connect();

    // Wait for connection
    await connectionPromise;
    expect(connection.isConnectionOpen()).toBe(true);

    // Disconnect and wait for disconnection event
    await connection.disconnect();
    await disconnectionPromise;
    expect(connection.isConnectionOpen()).toBe(false);
  });

  it('should handle ping/pong messages', (done) => {
    connection = new KioskConnection({
      url: TEST_URL,
      kioskId: 'test-kiosk'
    });

    connection.on('connected', () => {
      mockServer.emit('message', 'ping');
    });

    connection.on('ping', () => {
      try {
        expect(connection.isConnectionOpen()).toBe(true);
        done();
      } catch (error) {
        done(error);
      }
    });

    connection.connect();
  });

  it('should attempt reconnection on disconnect', async () => {
    let newServer: Server | null = null;
    const maxRetries = 2;
    const retryDelay = 100;

    // Create promises for tracking connection states
    let connectCount = 0;
    const firstConnect = new Promise<void>(resolve => {
      connection = new KioskConnection({
        url: TEST_URL,
        kioskId: 'test-kiosk',
        maxRetries,
        retryDelay
      });

      connection.on('connected', () => {
        connectCount++;
        if (connectCount === 1) {
          resolve();
        }
      });
    });

    const disconnect = new Promise<void>(resolve => {
      connection.on('disconnected', resolve);
    });

    const secondConnect = new Promise<void>(resolve => {
      connection.on('connected', () => {
        if (connectCount === 2) {
          resolve();
        }
      });
    });

    // Start the connection process
    connection.connect();

    // Wait for first connection
    await firstConnect;
    expect(connection.isConnectionOpen()).toBe(true);

    // Stop the server and create a new one after a delay
    mockServer.stop();
    await disconnect;

    // Create new server
    await new Promise(resolve => setTimeout(resolve, retryDelay / 2));
    newServer = new Server(TEST_URL);

    // Wait for second connection
    await secondConnect;
    expect(connection.isConnectionOpen()).toBe(true);

    // Cleanup
    if (newServer) {
      newServer.stop();
    }
  }, 10000); // Increased timeout for stability
});

describe('KioskSDK', () => {
  let mockServer: Server;
  const TEST_URL = 'ws://localhost:8080';

  beforeEach(() => {
    mockServer = new Server(TEST_URL);
  });

  afterEach(async () => {
    await kioskSDK.disconnect();
    mockServer.stop();
    jest.clearAllTimers();
  });

  it('should initialize monitoring with connection update', (done) => {
    const connection = kioskSDK.initializeMonitoring({
      url: TEST_URL,
      onConnectionUpdate: (connected: boolean) => {
        if (connected) {
          try {
            expect(connection.isConnectionOpen()).toBe(true);
            done();
          } catch (error) {
            done(error);
          }
        }
      }
    });

    expect(connection).toBeDefined();
  });
});