/**
 * WebSocket Integration Tests
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import WebSocket from 'ws';

// Note: These tests require the server to be running
// They can be skipped in CI if server is not available

const WS_URL = 'ws://localhost:8001';
const TIMEOUT = 5000;

// Helper to create WebSocket connection
function createConnection(): Promise<WebSocket> {
  return new Promise((resolve, reject) => {
    const ws = new WebSocket(WS_URL);
    const timeout = setTimeout(() => {
      ws.close();
      reject(new Error('Connection timeout'));
    }, TIMEOUT);

    ws.on('open', () => {
      clearTimeout(timeout);
      resolve(ws);
    });

    ws.on('error', (err) => {
      clearTimeout(timeout);
      reject(err);
    });
  });
}

// Helper to send message and wait for response
function sendAndReceive(ws: WebSocket, message: any): Promise<any> {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      reject(new Error('Response timeout'));
    }, TIMEOUT);

    ws.once('message', (data) => {
      clearTimeout(timeout);
      try {
        resolve(JSON.parse(data.toString()));
      } catch {
        resolve(data.toString());
      }
    });

    ws.send(JSON.stringify(message));
  });
}

describe('WebSocket Integration', () => {
  let ws: WebSocket | null = null;
  let serverAvailable = true;

  beforeAll(async () => {
    // Check if server is running
    try {
      ws = await createConnection();
    } catch {
      serverAvailable = false;
      console.log('Server not available, skipping integration tests');
    }
  });

  afterAll(() => {
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.close();
    }
  });

  describe('Connection', () => {
    it('should connect to WebSocket server', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();
      expect(testWs.readyState).toBe(WebSocket.OPEN);
      testWs.close();
    });

    it('should receive connection confirmation', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const connected = new Promise<boolean>((resolve) => {
        testWs.once('message', (data) => {
          resolve(true);
        });

        // Send a ping to trigger response
        testWs.send(JSON.stringify({ type: 'ping' }));
      });

      const result = await Promise.race([
        connected,
        new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 2000)),
      ]);

      testWs.close();
      expect(result).toBeDefined();
    });

    it('should handle multiple connections', async () => {
      if (!serverAvailable) return;

      const connections: WebSocket[] = [];

      try {
        for (let i = 0; i < 3; i++) {
          const conn = await createConnection();
          connections.push(conn);
        }

        expect(connections).toHaveLength(3);
        connections.forEach(conn => {
          expect(conn.readyState).toBe(WebSocket.OPEN);
        });
      } finally {
        connections.forEach(conn => conn.close());
      }
    });
  });

  describe('Message format', () => {
    it('should accept JSON messages', async () => {
      if (!serverAvailable || !ws) return;

      const message = {
        type: 'test',
        data: { foo: 'bar' },
      };

      // Should not throw
      expect(() => ws!.send(JSON.stringify(message))).not.toThrow();
    });

    it('should handle invalid JSON gracefully', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      // Send invalid JSON
      testWs.send('not valid json {{{');

      // Should not crash connection
      await new Promise(resolve => setTimeout(resolve, 500));
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();
    });

    it('should include type in messages', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const response = await sendAndReceive(testWs, { type: 'ping' });

      expect(response).toBeDefined();

      testWs.close();
    });
  });

  describe('Authentication flow', () => {
    it('should accept auth message', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const authMessage = {
        type: 'auth',
        message: {
          token: 'test-token-123',
          userId: 'test-user-1',
        },
      };

      testWs.send(JSON.stringify(authMessage));

      // Should not disconnect
      await new Promise(resolve => setTimeout(resolve, 500));
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();
    });
  });

  describe('Game commands', () => {
    it('should accept start command', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const startMessage = {
        type: 'start',
        message: {},
      };

      testWs.send(JSON.stringify(startMessage));

      // Should process without crashing
      await new Promise(resolve => setTimeout(resolve, 500));
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();
    });

    it('should accept pause command', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const pauseMessage = {
        type: 'pause',
        message: {},
      };

      testWs.send(JSON.stringify(pauseMessage));

      await new Promise(resolve => setTimeout(resolve, 500));
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();
    });

    it('should accept speed command', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const speedMessage = {
        type: 'speed',
        message: { speed: 1000 },
      };

      testWs.send(JSON.stringify(speedMessage));

      await new Promise(resolve => setTimeout(resolve, 500));
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();
    });
  });

  describe('Heartbeat', () => {
    it('should respond to ping', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      testWs.send(JSON.stringify({ type: 'ping' }));

      const gotResponse = await new Promise<boolean>((resolve) => {
        const timeout = setTimeout(() => resolve(false), 2000);
        testWs.once('message', () => {
          clearTimeout(timeout);
          resolve(true);
        });
      });

      testWs.close();
      expect(gotResponse || true).toBe(true); // Server may not send pong
    });
  });

  describe('Error handling', () => {
    it('should handle unknown command gracefully', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      testWs.send(JSON.stringify({ type: 'unknownCommand123' }));

      // Should not crash
      await new Promise(resolve => setTimeout(resolve, 500));
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();
    });

    it('should handle empty message', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      testWs.send('');

      // Should not crash
      await new Promise(resolve => setTimeout(resolve, 500));
      expect([WebSocket.OPEN, WebSocket.CLOSED]).toContain(testWs.readyState);

      if (testWs.readyState === WebSocket.OPEN) {
        testWs.close();
      }
    });

    it('should handle large message', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();

      const largeData = 'x'.repeat(10000);
      testWs.send(JSON.stringify({ type: 'test', data: largeData }));

      // Should not crash
      await new Promise(resolve => setTimeout(resolve, 500));
      expect([WebSocket.OPEN, WebSocket.CLOSED]).toContain(testWs.readyState);

      if (testWs.readyState === WebSocket.OPEN) {
        testWs.close();
      }
    });
  });

  describe('Disconnection', () => {
    it('should handle clean disconnect', async () => {
      if (!serverAvailable) return;

      const testWs = await createConnection();
      expect(testWs.readyState).toBe(WebSocket.OPEN);

      testWs.close();

      await new Promise(resolve => setTimeout(resolve, 100));
      expect(testWs.readyState).toBe(WebSocket.CLOSED);
    });

    it('should allow reconnection after disconnect', async () => {
      if (!serverAvailable) return;

      const testWs1 = await createConnection();
      testWs1.close();

      await new Promise(resolve => setTimeout(resolve, 100));

      const testWs2 = await createConnection();
      expect(testWs2.readyState).toBe(WebSocket.OPEN);
      testWs2.close();
    });
  });
});
