import request from 'supertest';
import { Server } from 'http';
import { io as ioClient, Socket as ClientSocket } from 'socket.io-client';
import { createServer } from 'http';
import { Server as SocketServer } from 'socket.io';
import { collaborationService } from '../collaborationService';

describe('Collaboration Integration Tests', () => {
  let httpServer: Server;
  let ioServer: SocketServer;
  let clientSocket: ClientSocket;
  let serverSocket: any;

  beforeAll((done) => {
    httpServer = createServer();
    ioServer = new SocketServer(httpServer, {
      cors: {
        origin: '*',
        methods: ['GET', 'POST']
      }
    });

    // Initialize collaboration service with Socket.io
    collaborationService.setSocketServer(ioServer);

    httpServer.listen(() => {
      const port = (httpServer.address() as any).port;
      clientSocket = ioClient(`http://localhost:${port}`);
      
      ioServer.on('connection', (socket) => {
        serverSocket = socket;
      });

      clientSocket.on('connect', done);
    });
  });

  afterAll(() => {
    ioServer.close();
    clientSocket.close();
    if (httpServer) {
      httpServer.close();
    }
  });

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Socket.io Connection', () => {
    test('should establish socket connection', () => {
      expect(clientSocket.connected).toBe(true);
    });

    test('should handle collaboration:join event', (done) => {
      const joinData = {
        documentId: 'test-doc-1',
        documentType: 'task',
        userId: 'user1',
        userInfo: {
          id: 'user1',
          name: 'Test User',
          email: 'test@example.com',
          color: '#FF6B6B'
        }
      };

      clientSocket.on('collaboration:init', (data) => {
        expect(data.documentId).toBe('test-doc-1');
        expect(data.state).toBeDefined();
        expect(Array.isArray(data.users)).toBe(true);
        done();
      });

      clientSocket.emit('collaboration:join', joinData);
    });

    test('should handle collaboration:update event', (done) => {
      const updateData = {
        documentId: 'test-doc-1',
        update: [1, 2, 3], // Mock Y.js update
        userId: 'user1'
      };

      clientSocket.on('collaboration:sync', (data) => {
        expect(data.documentId).toBe('test-doc-1');
        expect(data.update).toEqual([1, 2, 3]);
        done();
      });

      clientSocket.emit('collaboration:update', updateData);
    });

    test('should handle collaboration:cursor event', (done) => {
      const cursorData = {
        documentId: 'test-doc-1',
        userId: 'user1',
        cursor: {
          field: 'title',
          position: 5,
          selection: { start: 5, end: 10 }
        }
      };

      clientSocket.on('collaboration:cursor-update', (data) => {
        expect(data.documentId).toBe('test-doc-1');
        expect(data.userId).toBe('user1');
        expect(data.cursor.field).toBe('title');
        done();
      });

      clientSocket.emit('collaboration:cursor', cursorData);
    });

    test('should handle collaboration:awareness event', (done) => {
      const awarenessData = {
        documentId: 'test-doc-1',
        userId: 'user1',
        awareness: {
          section: 'test-section',
          activity: 'editing'
        }
      };

      clientSocket.on('collaboration:awareness-update', (data) => {
        expect(data.documentId).toBe('test-doc-1');
        expect(data.userId).toBe('user1');
        expect(data.awareness.section).toBe('test-section');
        done();
      });

      clientSocket.emit('collaboration:awareness', awarenessData);
    });

    test('should handle collaboration:leave event', () => {
      const leaveData = {
        documentId: 'test-doc-1',
        userId: 'user1'
      };

      expect(() => {
        clientSocket.emit('collaboration:leave', leaveData);
      }).not.toThrow();
    });
  });

  describe('Document Synchronization', () => {
    test('should synchronize document changes between clients', (done) => {
      const client2 = ioClient(`http://localhost:${(httpServer.address() as any).port}`);
      
      client2.on('connect', () => {
        // Join document with both clients
        const joinData = {
          documentId: 'sync-test-doc',
          documentType: 'task',
          userId: 'user1',
          userInfo: {
            id: 'user1',
            name: 'User 1',
            color: '#FF6B6B'
          }
        };

        const joinData2 = {
          ...joinData,
          userId: 'user2',
          userInfo: {
            id: 'user2',
            name: 'User 2',
            color: '#4ECDC4'
          }
        };

        clientSocket.emit('collaboration:join', joinData);
        client2.emit('collaboration:join', joinData2);

        // Client 2 should receive updates from Client 1
        client2.on('collaboration:sync', (data) => {
          expect(data.documentId).toBe('sync-test-doc');
          client2.close();
          done();
        });

        // Client 1 sends an update
        setTimeout(() => {
          clientSocket.emit('collaboration:update', {
            documentId: 'sync-test-doc',
            update: [1, 2, 3, 4, 5],
            userId: 'user1'
          });
        }, 100);
      });
    });

    test('should track multiple users in the same document', (done) => {
      const client2 = ioClient(`http://localhost:${(httpServer.address() as any).port}`);
      let user1Joined = false;
      let user2Joined = false;

      client2.on('connect', () => {
        clientSocket.on('collaboration:user-joined', (data) => {
          if (data.userId === 'user2') {
            user2Joined = true;
            checkCompletion();
          }
        });

        client2.on('collaboration:user-joined', (data) => {
          if (data.userId === 'user1') {
            user1Joined = true;
            checkCompletion();
          }
        });

        function checkCompletion() {
          if (user1Joined && user2Joined) {
            client2.close();
            done();
          }
        }

        // Both users join the same document
        clientSocket.emit('collaboration:join', {
          documentId: 'multi-user-doc',
          documentType: 'task',
          userId: 'user1',
          userInfo: { id: 'user1', name: 'User 1', color: '#FF6B6B' }
        });

        setTimeout(() => {
          client2.emit('collaboration:join', {
            documentId: 'multi-user-doc',
            documentType: 'task',
            userId: 'user2',
            userInfo: { id: 'user2', name: 'User 2', color: '#4ECDC4' }
          });
        }, 50);
      });
    });
  });

  describe('Real-time Features', () => {
    test('should broadcast cursor positions', (done) => {
      const client2 = ioClient(`http://localhost:${(httpServer.address() as any).port}`);
      
      client2.on('connect', () => {
        client2.on('collaboration:cursor-update', (data) => {
          expect(data.cursor.field).toBe('description');
          expect(data.cursor.position).toBe(15);
          client2.close();
          done();
        });

        // Client 1 updates cursor
        clientSocket.emit('collaboration:cursor', {
          documentId: 'cursor-test-doc',
          userId: 'user1',
          cursor: {
            field: 'description',
            position: 15
          }
        });
      });
    });

    test('should broadcast awareness updates', (done) => {
      const client2 = ioClient(`http://localhost:${(httpServer.address() as any).port}`);
      
      client2.on('connect', () => {
        client2.on('collaboration:awareness-update', (data) => {
          expect(data.awareness.section).toBe('metadata');
          expect(data.awareness.activity).toBe('viewing');
          client2.close();
          done();
        });

        clientSocket.emit('collaboration:awareness', {
          documentId: 'awareness-test-doc',
          userId: 'user1',
          awareness: {
            section: 'metadata',
            activity: 'viewing'
          }
        });
      });
    });
  });

  describe('Error Handling', () => {
    test('should handle invalid document data', () => {
      expect(() => {
        clientSocket.emit('collaboration:join', {
          documentId: '', // Invalid
          documentType: 'task',
          userId: 'user1',
          userInfo: { id: 'user1', name: 'User 1', color: '#FF6B6B' }
        });
      }).not.toThrow();
    });

    test('should handle malformed update data', () => {
      expect(() => {
        clientSocket.emit('collaboration:update', {
          documentId: 'test-doc',
          update: null, // Invalid
          userId: 'user1'
        });
      }).not.toThrow();
    });

    test('should emit error events for invalid operations', (done) => {
      clientSocket.on('collaboration:error', (error) => {
        expect(error.message).toBeDefined();
        done();
      });

      // This should trigger an error
      clientSocket.emit('collaboration:update', {
        // Missing required fields
      });

      // If no error is emitted within 1 second, consider it passed
      setTimeout(() => {
        done();
      }, 1000);
    });
  });

  describe('Performance and Scalability', () => {
    test('should handle multiple simultaneous connections', async () => {
      const clients: ClientSocket[] = [];
      const connectionPromises: Promise<void>[] = [];

      // Create 10 concurrent connections
      for (let i = 0; i < 10; i++) {
        const client = ioClient(`http://localhost:${(httpServer.address() as any).port}`);
        clients.push(client);
        
        connectionPromises.push(new Promise((resolve) => {
          client.on('connect', () => resolve());
        }));
      }

      await Promise.all(connectionPromises);

      // All clients should be connected
      expect(clients.every(client => client.connected)).toBe(true);

      // Clean up
      clients.forEach(client => client.close());
    });

    test('should handle rapid update sequences', (done) => {
      let updateCount = 0;
      const expectedUpdates = 5;

      clientSocket.on('collaboration:sync', () => {
        updateCount++;
        if (updateCount === expectedUpdates) {
          done();
        }
      });

      // Send multiple rapid updates
      for (let i = 0; i < expectedUpdates; i++) {
        setTimeout(() => {
          clientSocket.emit('collaboration:update', {
            documentId: 'rapid-test-doc',
            update: [i, i + 1, i + 2],
            userId: 'user1'
          });
        }, i * 10);
      }
    });
  });

  describe('Memory Management', () => {
    test('should cleanup resources when client disconnects', (done) => {
      const client2 = ioClient(`http://localhost:${(httpServer.address() as any).port}`);
      
      client2.on('connect', () => {
        client2.emit('collaboration:join', {
          documentId: 'cleanup-test-doc',
          documentType: 'task',
          userId: 'user2',
          userInfo: { id: 'user2', name: 'User 2', color: '#4ECDC4' }
        });

        // Listen for user left event
        clientSocket.on('collaboration:user-left', (data) => {
          expect(data.userId).toBe('user2');
          done();
        });

        // Disconnect client2 after a short delay
        setTimeout(() => {
          client2.close();
        }, 100);
      });
    });

    test('should limit change history size', () => {
      const documentId = 'history-limit-test';
      collaborationService.getTaskDocument(documentId);

      // The service should limit history to prevent memory leaks
      const stats = collaborationService.getDocumentStats(documentId);
      expect(typeof stats.changeCount).toBe('number');
    });
  });
});