/**
 * Integration Tests for Collaboration Layer
 * End-to-end tests for real-time collaboration features
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { Doc, encodeStateAsUpdate, applyUpdate, UndoManager } from 'yjs';
import { WebSocketConnection } from './websocket-connection';
import { CRDTDocument } from './crdt-document';
import { ChangeTracker } from './change-tracker';
import { MergeLogic, ServerChange } from './merge-logic';
import { PresenceManager } from './presence-manager';
import { IDBPersistence } from './idb-persistence';
import { UpdateBatcher } from './update-batcher';
import { CollaborationBenchmark } from './benchmark';

/**
 * Mock WebSocket provider for testing
 */
class MockWebSocketProvider {
  public wsconnected: boolean = false;
  public doc: Doc;
  public awareness: any;
  public messageChannel: any;

  constructor(doc: Doc) {
    this.doc = doc;
    this.awareness = {
      setLocalStateField: () => {},
      getLocalState: () => ({}),
    };
  }

  connect() {
    this.wsconnected = true;
  }

  disconnect() {
    this.wsconnected = false;
  }

  destroy() {
    this.disconnect();
  }

  on(_event: string, _callback: any) {}
  off(_event: string, _callback: any) {}
}

describe('Collaboration Layer Integration', () => {
  describe('Multi-User Sync Flow', () => {
    it('should sync changes between two users', async () => {
      // Simulate two users editing the same document
      const doc1 = new Doc();
      const doc2 = new Doc();

      const text1 = doc1.getText('main');
      const text2 = doc2.getText('main');

      // User 1 types "Hello"
      text1.insert(0, 'Hello');
      const update1 = encodeStateAsUpdate(doc1);

      // Sync to User 2
      applyUpdate(doc2, update1);

      expect(text2.toString()).toBe('Hello');

      // User 2 types " World"
      text2.insert(5, ' World');
      const update2 = encodeStateAsUpdate(doc2);

      // Sync back to User 1
      applyUpdate(doc1, update2);

      expect(text1.toString()).toBe('Hello World');
      expect(text2.toString()).toBe('Hello World');
    });

    it('should handle concurrent edits without conflicts', () => {
      const doc1 = new Doc();
      const doc2 = new Doc();

      // Start with same content
      const text1 = doc1.getText('main');
      const text2 = doc2.getText('main');
      text1.insert(0, 'Initial');
      const initialUpdate = encodeStateAsUpdate(doc1);
      applyUpdate(doc2, initialUpdate);

      // Concurrent edits at different positions
      text1.insert(7, ' A');
      text2.insert(7, ' B');

      // Exchange updates
      const update1 = encodeStateAsUpdate(doc1);
      const update2 = encodeStateAsUpdate(doc2);

      applyUpdate(doc2, update1);
      applyUpdate(doc1, update2);

      // Both should converge to same content
      expect(text1.toString()).toBe(text2.toString());
    });
  });

  describe('Change Tracking Integration', () => {
    it('should track changes from multiple users', () => {
      const doc = new Doc();
      const tracker1 = new ChangeTracker(doc, 'user-1', 'Alice');
      const tracker2 = new ChangeTracker(doc, 'user-2', 'Bob');

      // User 1 makes a change
      const text = doc.getText('main');
      text.insert(0, 'Hello');

      // User 2 makes a change
      text.insert(5, ' World');

      const changes1 = tracker1.getAllChanges();
      const changes2 = tracker2.getAllChanges();

      // Both trackers should see changes
      expect(changes1.length).toBeGreaterThan(0);
      expect(changes2.length).toBeGreaterThan(0);
    });

    it('should create and restore snapshots', () => {
      const doc = new Doc();
      const tracker = new ChangeTracker(doc, 'user-1', 'Alice');

      // Initial state
      const text = doc.getText('main');
      text.insert(0, 'Version 1');

      // Create snapshot
      const snapshot1 = tracker.createSnapshot('Version 1');

      // Make changes
      text.delete(0, 9);
      text.insert(0, 'Version 2');

      expect(text.toString()).toBe('Version 2');

      // Restore snapshot
      tracker.restoreSnapshot(snapshot1);

      expect(text.toString()).toBe('Version 1');
    });
  });

  describe('Merge Logic Integration', () => {
    it('should auto-resolve non-conflicting edits', () => {
      const doc = new Doc();
      const mergeLogic = new MergeLogic(doc, 'user-1');

      // Initialize document
      const text = doc.getText('main');
      text.insert(0, 'Initial content here');

      // Remote change at different position
      const remoteChange: ServerChange = {
        id: 'change-1',
        userId: 'user-2',
        timestamp: new Date(),
        type: 'insert',
        position: 20,
        content: ' appended',
        length: 9,
      };

      const result = mergeLogic.applyServerChange(remoteChange);

      expect(result.success).toBe(true);
      expect(result.conflicts.length).toBe(0);
    });

    it('should detect concurrent edit conflicts', () => {
      const doc = new Doc();
      const mergeLogic = new MergeLogic(doc, 'user-1');

      const text = doc.getText('main');
      text.insert(0, 'Initial');

      // Simulate local change
      text.insert(7, ' Local');

      // Remote change at overlapping position
      const remoteChange: ServerChange = {
        id: 'change-1',
        userId: 'user-2',
        timestamp: new Date(),
        type: 'insert',
        position: 5,
        content: ' Remote',
        length: 7,
      };

      const result = mergeLogic.applyServerChange(remoteChange);

      // Should handle the conflict
      expect(result).toBeDefined();
    });
  });

  describe('Presence Integration', () => {
    it('should track user presence', () => {
      const presenceManager = new PresenceManager({
        userId: 'user-1',
        userName: 'Alice',
        userColor: '#3b82f6',
        idleTimeoutMs: 1000,
      });

      // Check initial state
      const localPresence = presenceManager.getLocalPresence();
      expect(localPresence.userId).toBe('user-1');
      expect(localPresence.name).toBe('Alice');
      expect(localPresence.status).toBe('active');
    });

    it('should detect idle state', async () => {
      const presenceManager = new PresenceManager({
        userId: 'user-1',
        userName: 'Alice',
        userColor: '#3b82f6',
        idleTimeoutMs: 100,
      });

      // Initialize with mock connection
      const mockDoc = new Doc();
      const mockProvider = new MockWebSocketProvider(mockDoc);
      const mockConnection: any = {
        getProvider: () => mockProvider,
      };

      presenceManager.initialize(mockConnection);

      // Manually set last activity to the past to simulate idle
      (presenceManager as any).lastActivityTime = new Date(Date.now() - 200);

      // Wait for idle check interval (1 second)
      await new Promise(resolve => setTimeout(resolve, 1500));
      
      const isIdle = presenceManager.isUserIdle('user-1');
      expect(isIdle).toBe(true);
      presenceManager.shutdown();
    });
  });

  describe('Persistence Integration', () => {
    it('should save and load document state', async () => {
      // Note: IndexedDB tests require browser environment
      // This is a placeholder for browser-based integration tests
      // In browser:
      //   const doc = new Doc();
      //   const text = doc.getText('main');
      //   text.insert(0, 'Test content');
      //   const persistence = new IDBPersistence(doc, { dbName: 'test-frenocorp', autoSave: false });
      //   await persistence.save('test-doc');
      //   const loaded = await persistence.load('test-doc');
      //   persistence.destroy();

      // Test passes if no errors thrown
      expect(true).toBe(true);
    });
  });

  describe('Performance Integration', () => {
   it('should batch updates efficiently', () => {
      const mockDoc = new Doc();
      const batcher = new UpdateBatcher({
        maxBatchSize: 10,
        maxWaitMs: 1000,
      });

      // Queue multiple updates (without provider for unit test)
      for (let i = 0; i < 5; i++) {
        const update = new Uint8Array([i]);
        batcher.queueUpdate(update);
      }

      const stats = batcher.getStats();
      expect(stats.pendingUpdates).toBe(5); // Queued but not flushed without provider
      expect(stats.totalBatchesSent).toBe(0);
    });

    it('should measure sync latency', () => {
      const benchmark = new CollaborationBenchmark(true);

      benchmark.startOperation('sync-test');
      // Simulate sync operation
      const start = performance.now();
      while (performance.now() - start < 10) {} // 10ms delay
      const result = benchmark.endOperation('sync-test');

      expect(result).toBeDefined();
      expect(result!.duration).toBeGreaterThanOrEqual(10);

      benchmark.disable();
    });

    it('should track memory usage', () => {
      const benchmark = new CollaborationBenchmark(true);
      const memory = benchmark.getMemoryMetrics();

      // In browser with memory API, should return metrics
      // In Node.js or browser without API, returns null
      // Test passes either way
      expect(memory === null || typeof memory.heapUsed === 'number').toBe(true);

      benchmark.disable();
    });
  });

  describe('End-to-End Collaboration Scenario', () => {
    it('should handle full collaboration workflow', () => {
      // Setup: Two users collaborating on a screenplay
      const doc = new Doc();
      const text = doc.getText('main');

      const tracker1 = new ChangeTracker(doc, 'user-1', 'Alice');
      const tracker2 = new ChangeTracker(doc, 'user-2', 'Bob');

      const mergeLogic1 = new MergeLogic(doc, 'user-1');
      const mergeLogic2 = new MergeLogic(doc, 'user-2');

      const benchmark = new CollaborationBenchmark(true);

      // Scene 1: Alice writes opening
      benchmark.startOperation('alice-type-1');
      text.insert(0, 'FADE IN:\n\nINT. COFFEE SHOP - DAY\n\n');
      benchmark.endOperation('alice-type-1');

      // Sync to Bob
      const update1 = encodeStateAsUpdate(doc);
      // (In real scenario, would apply to Bob's doc)

      // Scene 2: Bob adds dialogue
      benchmark.startOperation('bob-type-1');
      const position = text.toString().length;
      text.insert(position, 'ALEX\nGood morning!\n');
      benchmark.endOperation('bob-type-1');

      // Scene 3: Alice makes concurrent edit
      benchmark.startOperation('alice-type-2');
      text.insert(10, '(busy)\n');
      benchmark.endOperation('alice-type-2');

      // Check change tracking
      const allChanges = tracker1.getAllChanges();
      expect(allChanges.length).toBeGreaterThan(0);

      // Create snapshot
      const snapshot = tracker1.createSnapshot('After opening scene');
      expect(snapshot).toBeDefined();

      // Check benchmark stats
      const aliceStats = benchmark.getOperationStats('alice-type-1');
      expect(aliceStats).toBeDefined();

      benchmark.disable();

      // Verify final document
      const finalContent = text.toString();
      expect(finalContent).toContain('FADE IN:');
      expect(finalContent).toContain('ALEX');
      expect(finalContent).toContain('Good morning!');
    });
  });
});

describe('Collaboration Layer Edge Cases', () => {
  it('should handle rapid consecutive updates', () => {
    const doc = new Doc();
    const text = doc.getText('main');

    // Simulate rapid typing
    for (let i = 0; i < 100; i++) {
      text.insert(i, String.fromCharCode(65 + (i % 26)));
    }

    expect(text.toString().length).toBe(100);
  });

  it('should handle large documents', () => {
    const doc = new Doc();
    const text = doc.getText('main');

    // Insert 10KB of content
    const largeContent = 'A'.repeat(10000);
    text.insert(0, largeContent);

    expect(text.toString().length).toBe(10000);

    const update = encodeStateAsUpdate(doc);
    expect(update.length).toBeGreaterThan(0);
  });

  it('should handle disconnection and reconnection', () => {
    const doc = new Doc();
    const text = doc.getText('main');

    // Initial state
    text.insert(0, 'Initial');
    const state1 = encodeStateAsUpdate(doc);

    // Simulate disconnection - make changes offline
    text.insert(7, ' Offline');

    // Reconnection - should sync
    const state2 = encodeStateAsUpdate(doc);
    expect(state2.length).toBeGreaterThan(state1.length);
  });

  it('should handle undo/redo during collaboration', () => {
    const doc = new Doc();
    const text = doc.getText('main');
    const undoManager = new UndoManager([text]);

    // Make changes
    text.insert(0, 'Hello');
    undoManager.stopCapturing();
    text.insert(5, ' World');

    expect(text.toString()).toBe('Hello World');

    // Undo
    undoManager.undo();
    expect(text.toString()).toBe('Hello');

    // Redo
    undoManager.redo();
    expect(text.toString()).toBe('Hello World');
  });
});
