import { test, expect } from '../../fixtures/supersync.fixture';
import {
  createTestUser,
  getSuperSyncConfig,
  createSimulatedClient,
  closeClient,
  waitForTask,
  type SimulatedE2EClient,
} from '../../utils/supersync-helpers';
import { ImportPage } from '../../pages/import.page';
import { expectTaskOnAllClients } from '../../utils/supersync-assertions';

/**
 * SuperSync Vector Clock Pruning E2E Tests
 *
 * These tests verify the fix for the vector clock pruning bug that caused
 * new operations to be incorrectly filtered after a SYNC_IMPORT.
 *
 * BUG SCENARIO:
 * 1. Client A creates SYNC_IMPORT with clock {A: 1}
 * 2. Client B receives it, merges clocks → {A: 1, B: x, ...other clients...}
 * 3. When B has 21+ clients, pruning triggers (MAX_VECTOR_CLOCK_SIZE = 20)
 * 4. A's entry (counter=1, lowest) gets PRUNED
 * 5. New tasks from B have clock {B: y} - MISSING A's entry!
 * 6. Comparison: {A: 0} vs {A: 1} → CONCURRENT
 * 7. Tasks incorrectly filtered as "invalidated by SYNC_IMPORT"
 *
 * FIX:
 * - After applying SYNC_IMPORT, store the import client ID as "protected"
 * - limitVectorClockSize() preserves protected client IDs even with low counters
 * - New ops include the import client entry → comparison yields GREATER_THAN
 *
 * NOTE: E2E tests can't easily simulate 91+ clients to trigger pruning.
 * These tests verify the end-to-end flow that would fail without the fix.
 * Unit tests in vector-clock.spec.ts verify the pruning-specific behavior.
 *
 * Prerequisites:
 * - super-sync-server running on localhost:1901 with TEST_MODE=true
 * - Frontend running on localhost:4242
 *
 * Run with: npm run e2e:supersync:file e2e/tests/sync/supersync-vector-clock-pruning.spec.ts
 */

test.describe.configure({ mode: 'serial' });

test.describe('@supersync @pruning Vector Clock Pruning Fix', () => {
  /**
   * Scenario: Tasks created after receiving SYNC_IMPORT sync correctly
   *
   * This test verifies the core fix: after receiving a SYNC_IMPORT, the
   * receiving client can create new tasks that sync back to the import client.
   *
   * Without the fix, these tasks would be filtered as "concurrent with import"
   * because their vector clocks wouldn't include the import client's entry
   * (it would be pruned due to having the lowest counter value).
   *
   * Setup: Client A and B with shared SuperSync account
   *
   * Actions:
   * 1. Client A imports backup (creates SYNC_IMPORT)
   * 2. Client A syncs (uploads SYNC_IMPORT)
   * 3. Client B syncs (receives SYNC_IMPORT, merges clocks)
   * 4. Client B creates new tasks
   * 5. Client B syncs (uploads tasks)
   * 6. Client A syncs (downloads tasks from B)
   *
   * Verify:
   * - Both clients have the tasks created by B after the import
   * - No sync errors or data loss
   */
  test('Tasks created after receiving SYNC_IMPORT sync correctly', async ({
    browser,
    baseURL,
    testRunId,
  }) => {
    const uniqueId = `${testRunId}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
    let clientA: SimulatedE2EClient | null = null;
    let clientB: SimulatedE2EClient | null = null;

    try {
      const user = await createTestUser(testRunId);
      const syncConfig = getSuperSyncConfig(user);

      // ============ PHASE 1: Setup Both Clients ============
      console.log('[VC Pruning] Phase 1: Setting up both clients');

      clientA = await createSimulatedClient(browser, baseURL!, 'A', testRunId);
      await clientA.sync.setupSuperSync(syncConfig);

      clientB = await createSimulatedClient(browser, baseURL!, 'B', testRunId);
      await clientB.sync.setupSuperSync(syncConfig);

      // Initial sync to establish connection
      await clientA.sync.syncAndWait();
      await clientB.sync.syncAndWait();
      console.log('[VC Pruning] Both clients synced initially');

      // ============ PHASE 2: Client A Imports Backup ============
      console.log('[VC Pruning] Phase 2: Client A importing backup');

      const importPage = new ImportPage(clientA.page);
      await importPage.navigateToImportPage();

      // Import backup (creates SYNC_IMPORT with A's clock)
      const backupPath = ImportPage.getFixturePath('test-backup.json');
      await importPage.importBackupFile(backupPath);
      console.log('[VC Pruning] Client A imported backup');

      // Reload page after import - use goto instead of reload for reliability
      await clientA.page.goto(clientA.page.url(), {
        waitUntil: 'domcontentloaded',
        timeout: 30000,
      });
      await clientA.page.waitForLoadState('networkidle');

      // Re-enable sync after import - use 'local' to preserve imported data
      await clientA.sync.setupSuperSync({ ...syncConfig, syncImportChoice: 'local' });

      // Verify imported data is visible
      await waitForTask(clientA.page, 'E2E Import Test - Active Task With Subtask');
      console.log('[VC Pruning] Client A showing imported data');

      // ============ PHASE 3: Sync SYNC_IMPORT to Server ============
      console.log('[VC Pruning] Phase 3: Syncing SYNC_IMPORT to server');

      await clientA.sync.syncAndWait();
      console.log('[VC Pruning] Client A synced (SYNC_IMPORT uploaded)');

      // ============ PHASE 4: Client B Receives SYNC_IMPORT ============
      console.log('[VC Pruning] Phase 4: Client B receiving SYNC_IMPORT');

      await clientB.sync.syncAndWait();
      console.log('[VC Pruning] Client B synced (received SYNC_IMPORT)');

      // Navigate to work view and verify import applied
      await clientB.page.goto('/#/work-view');
      await clientB.page.waitForLoadState('networkidle');
      await waitForTask(clientB.page, 'E2E Import Test - Active Task With Subtask');
      console.log('[VC Pruning] Client B showing imported data');

      // ============ PHASE 5: Client B Creates New Tasks ============
      console.log('[VC Pruning] Phase 5: Client B creating new tasks after import');

      // Create multiple tasks to verify the fix works for multiple ops
      const task1 = `PostImport-Task1-${uniqueId}`;
      const task2 = `PostImport-Task2-${uniqueId}`;
      const task3 = `PostImport-Task3-${uniqueId}`;

      await clientB.workView.addTask(task1);
      await waitForTask(clientB.page, task1);
      console.log(`[VC Pruning] Client B created: ${task1}`);

      await clientB.workView.addTask(task2);
      await waitForTask(clientB.page, task2);
      console.log(`[VC Pruning] Client B created: ${task2}`);

      await clientB.workView.addTask(task3);
      await waitForTask(clientB.page, task3);
      console.log(`[VC Pruning] Client B created: ${task3}`);

      // ============ PHASE 6: Client B Syncs New Tasks ============
      console.log('[VC Pruning] Phase 6: Client B syncing new tasks');

      // This is the CRITICAL step - with the bug, these tasks would be filtered
      // by SyncImportFilterService as "concurrent with import" and not uploaded
      await clientB.sync.syncAndWait();
      console.log('[VC Pruning] Client B synced (new tasks uploaded)');

      // Check for sync errors (would appear if tasks were rejected)
      const errorSnack = clientB.page.locator('simple-snack-bar.error');
      await expect(errorSnack).not.toBeVisible({ timeout: 5000 });
      console.log('[VC Pruning] No sync errors on Client B');

      // ============ PHASE 7: Client A Downloads New Tasks ============
      console.log('[VC Pruning] Phase 7: Client A downloading new tasks from B');

      await clientA.sync.syncAndWait();
      console.log('[VC Pruning] Client A synced (downloaded B tasks)');

      // Navigate to work view
      await clientA.page.goto('/#/work-view');
      await clientA.page.waitForLoadState('networkidle');

      // ============ PHASE 8: Verify Both Clients Have All Tasks ============
      console.log('[VC Pruning] Phase 8: Verifying both clients have all tasks');

      // Verify tasks on Client A (originally created by B)
      await waitForTask(clientA.page, task1);
      await waitForTask(clientA.page, task2);
      await waitForTask(clientA.page, task3);
      console.log('[VC Pruning] Client A has all tasks created by B');

      // Verify tasks on Client B (confirm they still exist)
      await expect(clientB.page.locator(`task:has-text("${task1}")`)).toBeVisible();
      await expect(clientB.page.locator(`task:has-text("${task2}")`)).toBeVisible();
      await expect(clientB.page.locator(`task:has-text("${task3}")`)).toBeVisible();
      console.log('[VC Pruning] Client B still has all tasks');

      // Also verify imported task is on both
      await expectTaskOnAllClients(
        [clientA, clientB],
        'E2E Import Test - Active Task With Subtask',
      );

      console.log('[VC Pruning] Vector clock pruning fix E2E test PASSED!');
    } finally {
      if (clientA) await closeClient(clientA);
      if (clientB) await closeClient(clientB);
    }
  });

  /**
   * Scenario: Multiple sync cycles after SYNC_IMPORT maintain consistency
   *
   * This test verifies that the fix is stable across multiple sync cycles.
   * The import client's entry should remain protected across all operations.
   *
   * Actions:
   * 1. Client A imports backup, syncs
   * 2. Client B receives import, creates tasks, syncs
   * 3. Client A creates tasks, syncs
   * 4. Client B receives A's tasks, creates more tasks, syncs
   * 5. Verify all tasks are on both clients
   */
  test('Multiple sync cycles after SYNC_IMPORT maintain task consistency', async ({
    browser,
    baseURL,
    testRunId,
  }) => {
    const uniqueId = `${testRunId}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
    let clientA: SimulatedE2EClient | null = null;
    let clientB: SimulatedE2EClient | null = null;

    try {
      const user = await createTestUser(testRunId);
      const syncConfig = getSuperSyncConfig(user);

      // ============ Setup ============
      console.log('[Multi-Cycle] Setting up clients');

      clientA = await createSimulatedClient(browser, baseURL!, 'A', testRunId);
      await clientA.sync.setupSuperSync(syncConfig);

      clientB = await createSimulatedClient(browser, baseURL!, 'B', testRunId);
      await clientB.sync.setupSuperSync(syncConfig);

      await clientA.sync.syncAndWait();
      await clientB.sync.syncAndWait();

      // ============ Import Backup ============
      console.log('[Multi-Cycle] Client A importing backup');

      const importPage = new ImportPage(clientA.page);
      await importPage.navigateToImportPage();
      const backupPath = ImportPage.getFixturePath('test-backup.json');
      await importPage.importBackupFile(backupPath);

      await clientA.page.goto(clientA.page.url(), {
        waitUntil: 'domcontentloaded',
        timeout: 30000,
      });
      await clientA.page.waitForLoadState('networkidle');
      await clientA.sync.setupSuperSync({ ...syncConfig, syncImportChoice: 'local' });
      await waitForTask(clientA.page, 'E2E Import Test - Active Task With Subtask');

      await clientA.sync.syncAndWait();
      console.log('[Multi-Cycle] SYNC_IMPORT uploaded');

      // ============ Cycle 1: B receives import, creates task ============
      console.log('[Multi-Cycle] Cycle 1: B receives import and creates task');

      await clientB.sync.syncAndWait();
      await clientB.page.goto('/#/work-view');
      await clientB.page.waitForLoadState('networkidle');
      await waitForTask(clientB.page, 'E2E Import Test - Active Task With Subtask');

      const taskB1 = `TaskB1-${uniqueId}`;
      await clientB.workView.addTask(taskB1);
      await clientB.sync.syncAndWait();
      console.log(`[Multi-Cycle] B created and synced: ${taskB1}`);

      // ============ Cycle 2: A receives B's task, creates own task ============
      console.log('[Multi-Cycle] Cycle 2: A receives B task and creates own');

      await clientA.sync.syncAndWait();
      await clientA.page.goto('/#/work-view');
      await clientA.page.waitForLoadState('networkidle');
      await waitForTask(clientA.page, taskB1);

      const taskA1 = `TaskA1-${uniqueId}`;
      await clientA.workView.addTask(taskA1);
      await clientA.sync.syncAndWait();
      console.log(`[Multi-Cycle] A created and synced: ${taskA1}`);

      // ============ Cycle 3: B receives A's task, creates another ============
      console.log('[Multi-Cycle] Cycle 3: B receives A task and creates another');

      await clientB.sync.syncAndWait();
      await waitForTask(clientB.page, taskA1);

      const taskB2 = `TaskB2-${uniqueId}`;
      await clientB.workView.addTask(taskB2);
      await clientB.sync.syncAndWait();
      console.log(`[Multi-Cycle] B created and synced: ${taskB2}`);

      // ============ Final sync and verification ============
      console.log('[Multi-Cycle] Final sync and verification');

      await clientA.sync.syncAndWait();
      await clientA.page.goto('/#/work-view');
      await clientA.page.waitForLoadState('networkidle');
      await clientB.page.goto('/#/work-view');
      await clientB.page.waitForLoadState('networkidle');

      // All created tasks should be on both clients
      await expectTaskOnAllClients([clientA, clientB], taskB1);
      await expectTaskOnAllClients([clientA, clientB], taskA1);
      await expectTaskOnAllClients([clientA, clientB], taskB2);

      // Imported task should still be there
      await expectTaskOnAllClients(
        [clientA, clientB],
        'E2E Import Test - Active Task With Subtask',
      );

      // Check for errors
      const errorSnackA = clientA.page.locator('simple-snack-bar.error');
      const errorSnackB = clientB.page.locator('simple-snack-bar.error');
      await expect(errorSnackA).not.toBeVisible({ timeout: 5000 });
      await expect(errorSnackB).not.toBeVisible({ timeout: 5000 });

      console.log('[Multi-Cycle] Multi-cycle test PASSED!');
    } finally {
      if (clientA) await closeClient(clientA);
      if (clientB) await closeClient(clientB);
    }
  });

  // NOTE: The "Reload after SYNC_IMPORT" scenario is already covered by
  // supersync-superseded-clock-regression.spec.ts tests. That test verifies that
  // reloading after receiving SYNC_IMPORT doesn't cause superseded clock issues.
  // Combined with the pruning fix verified above, the full scenario is covered.
});
