import { CollaborationService } from '../collaborationService';
import * as Y from 'yjs';
import { Server as SocketServer } from 'socket.io';
import { createServer } from 'http';

// Mock Socket.io
jest.mock('socket.io');

describe('CollaborationService', () => {
  let collaborationService: CollaborationService;
  let mockIo: jest.Mocked<SocketServer>;

  beforeEach(() => {
    collaborationService = new CollaborationService();
    mockIo = {
      on: jest.fn(),
      to: jest.fn().mockReturnThis(),
      emit: jest.fn()
    } as any;
    collaborationService.setSocketServer(mockIo);
  });

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

  describe('Document Management', () => {
    test('should create task document with correct structure', () => {
      const taskId = 'test-task-1';
      const doc = collaborationService.getTaskDocument(taskId);

      expect(doc).toBeInstanceOf(Y.Doc);
      
      const taskData = doc.getMap('task');
      expect(taskData.get('id')).toBe(taskId);
      expect(taskData.get('title')).toBeInstanceOf(Y.Text);
      expect(taskData.get('description')).toBeInstanceOf(Y.Text);
      expect(taskData.get('status')).toBe('pending');
      expect(taskData.get('priority')).toBe('medium');
      expect(taskData.get('assignees')).toBeInstanceOf(Y.Array);
      expect(taskData.get('tags')).toBeInstanceOf(Y.Array);
      expect(taskData.get('subtasks')).toBeInstanceOf(Y.Array);
      expect(taskData.get('comments')).toBeInstanceOf(Y.Array);
      expect(taskData.get('customFields')).toBeInstanceOf(Y.Map);
    });

    test('should create project document with correct structure', () => {
      const projectId = 'test-project-1';
      const doc = collaborationService.getProjectDocument(projectId);

      expect(doc).toBeInstanceOf(Y.Doc);
      
      const projectData = doc.getMap('project');
      expect(projectData.get('id')).toBe(projectId);
      expect(projectData.get('name')).toBeInstanceOf(Y.Text);
      expect(projectData.get('description')).toBeInstanceOf(Y.Text);
      expect(projectData.get('tasks')).toBeInstanceOf(Y.Array);
      expect(projectData.get('members')).toBeInstanceOf(Y.Array);
      expect(projectData.get('settings')).toBeInstanceOf(Y.Map);
    });

    test('should reuse existing documents', () => {
      const taskId = 'test-task-1';
      const doc1 = collaborationService.getTaskDocument(taskId);
      const doc2 = collaborationService.getTaskDocument(taskId);

      expect(doc1).toBe(doc2);
    });
  });

  describe('Change Tracking', () => {
    test('should track changes when document is modified', () => {
      const taskId = 'test-task-1';
      const doc = collaborationService.getTaskDocument(taskId);
      const taskData = doc.getMap('task');
      
      // Apply change
      const titleText = taskData.get('title') as Y.Text;
      titleText.insert(0, 'New Title');

      // Check change history
      const history = collaborationService.getChangeHistory(taskId);
      expect(history.length).toBeGreaterThan(0);
    });

    test('should get detailed change history with filtering', () => {
      const taskId = 'test-task-1';
      collaborationService.getTaskDocument(taskId);

      // Mock some history
      const mockHistory = [
        {
          documentId: taskId,
          type: 'task' as const,
          field: 'title',
          operation: 'update' as const,
          userId: 'user1',
          timestamp: new Date(Date.now() - 60000),
          oldValue: 'Old Title',
          newValue: 'New Title'
        },
        {
          documentId: taskId,
          type: 'task' as const,
          field: 'status',
          operation: 'update' as const,
          userId: 'user2',
          timestamp: new Date(),
          oldValue: 'pending',
          newValue: 'in-progress'
        }
      ];

      // Add to internal history (would need access to private property for full test)
      const filteredHistory = collaborationService.getDetailedChangeHistory(taskId, {
        field: 'title',
        limit: 10
      });

      expect(Array.isArray(filteredHistory)).toBe(true);
    });
  });

  describe('Snapshot Management', () => {
    test('should create snapshot with current document state', () => {
      const taskId = 'test-task-1';
      const doc = collaborationService.getTaskDocument(taskId);
      const taskData = doc.getMap('task');
      
      // Modify document
      const titleText = taskData.get('title') as Y.Text;
      titleText.insert(0, 'Test Title');
      taskData.set('status', 'in-progress');

      const snapshot = collaborationService.createSnapshot(taskId);
      
      expect(snapshot).toBeDefined();
      expect(snapshot.title).toBe('Test Title');
      expect(snapshot.status).toBe('in-progress');
      expect(snapshot.id).toBe(taskId);
    });

    test('should create detailed snapshot with metadata', () => {
      const taskId = 'test-task-1';
      collaborationService.getTaskDocument(taskId);

      const detailedSnapshot = collaborationService.createDetailedSnapshot(
        taskId,
        'Test Snapshot',
        'Test description',
        'user1'
      );

      expect(detailedSnapshot).toBeDefined();
      expect(detailedSnapshot.name).toBe('Test Snapshot');
      expect(detailedSnapshot.description).toBe('Test description');
      expect(detailedSnapshot.userId).toBe('user1');
      expect(detailedSnapshot.timestamp).toBeInstanceOf(Date);
      expect(detailedSnapshot.data).toBeDefined();
      expect(typeof detailedSnapshot.size).toBe('number');
      expect(detailedSnapshot.isAutomatic).toBe(false);
    });

    test('should restore from snapshot', () => {
      const taskId = 'test-task-1';
      const doc = collaborationService.getTaskDocument(taskId);
      
      const snapshotData = {
        title: 'Restored Title',
        description: 'Restored Description',
        status: 'completed'
      };

      collaborationService.restoreFromSnapshot(taskId, snapshotData, 'user1');

      const taskData = doc.getMap('task');
      const titleText = taskData.get('title') as Y.Text;
      expect(titleText.toString()).toBe('Restored Title');
      expect(taskData.get('status')).toBe('completed');
    });
  });

  describe('User Management', () => {
    test('should track active users', () => {
      const documentId = 'test-doc-1';
      const userId1 = 'user1';
      const userId2 = 'user2';

      // Simulate adding users (would normally be done through socket handlers)
      // For now, test the getter method exists
      const activeUsers = collaborationService.getActiveUsers(documentId);
      expect(Array.isArray(activeUsers)).toBe(true);
    });

    test('should get active users with full info', () => {
      const documentId = 'test-doc-1';
      const usersWithInfo = collaborationService.getActiveUsersWithInfo(documentId);
      
      expect(Array.isArray(usersWithInfo)).toBe(true);
    });

    test('should get section awareness data', () => {
      const section = 'test-section';
      const awarenessData = collaborationService.getSectionAwareness(section);
      
      expect(Array.isArray(awarenessData)).toBe(true);
    });
  });

  describe('Version Comparison', () => {
    test('should compare versions and detect differences', () => {
      const versionA = {
        title: 'Original Title',
        description: 'Original Description',
        status: 'pending'
      };

      const versionB = {
        title: 'Updated Title',
        description: 'Original Description',
        priority: 'high'
      };

      const differences = collaborationService.compareVersions('test-doc', versionA, versionB);

      expect(differences).toHaveLength(3); // title modified, status removed, priority added
      
      const titleDiff = differences.find(d => d.field === 'title');
      expect(titleDiff?.type).toBe('modified');
      expect(titleDiff?.oldValue).toBe('Original Title');
      expect(titleDiff?.newValue).toBe('Updated Title');

      const statusDiff = differences.find(d => d.field === 'status');
      expect(statusDiff?.type).toBe('removed');

      const priorityDiff = differences.find(d => d.field === 'priority');
      expect(priorityDiff?.type).toBe('added');
    });
  });

  describe('Rollback Validation', () => {
    test('should validate rollback operation', () => {
      const documentId = 'test-doc-1';
      collaborationService.getTaskDocument(documentId);

      const targetSnapshot = {
        title: 'Old Title',
        status: 'pending'
      };

      const validation = collaborationService.validateRollback(documentId, targetSnapshot);

      expect(validation.isValid).toBe(true);
      expect(Array.isArray(validation.warnings)).toBe(true);
      expect(Array.isArray(validation.affectedFields)).toBe(true);
      expect(Array.isArray(validation.conflictingUsers)).toBe(true);
    });
  });

  describe('Document Statistics', () => {
    test('should get document statistics', () => {
      const documentId = 'test-doc-1';
      collaborationService.getTaskDocument(documentId);

      const stats = collaborationService.getDocumentStats(documentId);

      expect(stats).toBeDefined();
      expect(typeof stats.activeUsers).toBe('number');
      expect(typeof stats.changeCount).toBe('number');
      expect(typeof stats.size).toBe('number');
    });
  });

  describe('Cleanup', () => {
    test('should cleanup document resources', () => {
      const documentId = 'test-doc-1';
      collaborationService.getTaskDocument(documentId);

      // Should not throw
      expect(() => {
        collaborationService.cleanupDocument(documentId);
      }).not.toThrow();
    });
  });
});