import { test, expect } from '../../fixtures/multi-client';
import {
  installWebRTCTracking,
  waitForPeerConnected,
  isPeerStillConnected,
  waitForAudioFlow,
  waitForAudioStatsPresent,
  waitForVideoFlow,
  waitForOutboundVideoFlow,
  waitForInboundVideoFlow,
  dumpRtcDiagnostics,
  installAutoResumeAudioContext
} from '../../helpers/webrtc-helpers';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
import { ChatRoomPage } from '../../pages/chat-room.page';

/**
 * Screen sharing E2E tests: verify video, screen-share audio, and voice audio
 * flow correctly between users during screen sharing.
 *
 * Uses the same dedicated-browser-per-client infrastructure as voice tests.
 * getDisplayMedia is monkey-patched to return a synthetic canvas video stream
 * + 880 Hz oscillator audio, bypassing the browser picker dialog.
 */

const ALICE = { username: `alice_ss_${Date.now()}`, displayName: 'Alice', password: 'TestPass123!' };
const BOB = { username: `bob_ss_${Date.now()}`, displayName: 'Bob', password: 'TestPass123!' };
const SERVER_NAME = `SS Test ${Date.now()}`;
const VOICE_CHANNEL = 'General';

/** Register a user and navigate to /search. */
async function registerUser(page: import('@playwright/test').Page, user: typeof ALICE) {
  const registerPage = new RegisterPage(page);

  await registerPage.goto();
  await expect(registerPage.submitButton).toBeVisible();
  await registerPage.register(user.username, user.displayName, user.password);
  await expect(page).toHaveURL(/\/search/, { timeout: 15_000 });
}

/** Both users register -> Alice creates server -> Bob joins. */
async function setupServerWithBothUsers(
  alice: { page: import('@playwright/test').Page },
  bob: { page: import('@playwright/test').Page }
) {
  await registerUser(alice.page, ALICE);
  await registerUser(bob.page, BOB);

  // Alice creates server
  const aliceSearch = new ServerSearchPage(alice.page);

  await aliceSearch.createServer(SERVER_NAME, { description: 'Screen share E2E' });
  await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });

  // Bob joins server
  const bobSearch = new ServerSearchPage(bob.page);

  await bobSearch.joinServerFromSearch(SERVER_NAME);
  await expect(bob.page).toHaveURL(/\/room\//, { timeout: 15_000 });
}

/** Ensure voice channel exists and both users join it. */
async function joinVoiceTogether(
  alice: { page: import('@playwright/test').Page },
  bob: { page: import('@playwright/test').Page }
) {
  const aliceRoom = new ChatRoomPage(alice.page);
  const existingChannel = alice.page
    .locator('app-rooms-side-panel')
    .getByRole('button', { name: VOICE_CHANNEL, exact: true });

  if (await existingChannel.count() === 0) {
    await aliceRoom.openCreateVoiceChannelDialog();
    await aliceRoom.createChannel(VOICE_CHANNEL);
    await expect(existingChannel).toBeVisible({ timeout: 10_000 });
  }

  const bobRoom = new ChatRoomPage(bob.page);
  const doJoin = async () => {
    await aliceRoom.joinVoiceChannel(VOICE_CHANNEL);
    await expect(alice.page.locator('app-voice-controls')).toBeVisible({ timeout: 15_000 });

    await bobRoom.joinVoiceChannel(VOICE_CHANNEL);
    await expect(bob.page.locator('app-voice-controls')).toBeVisible({ timeout: 15_000 });

    // Wait for WebRTC + audio pipeline
    await waitForPeerConnected(alice.page, 30_000);
    await waitForPeerConnected(bob.page, 30_000);
    await waitForAudioStatsPresent(alice.page, 20_000);
    await waitForAudioStatsPresent(bob.page, 20_000);
  };

  await doJoin();

  // Chromium's --use-fake-device-for-media-stream can produce a silent
  // capture track on the very first getUserMedia call.  If bidirectional
  // audio doesn't flow within a short window, leave and rejoin voice to
  // re-acquire the mic (second getUserMedia on a warm device works).
  const aliceDelta = await waitForAudioFlow(alice.page, 10_000);
  const bobDelta = await waitForAudioFlow(bob.page, 10_000);
  const aliceFlowing =
    (aliceDelta.outboundBytesDelta > 0 || aliceDelta.outboundPacketsDelta > 0) &&
    (aliceDelta.inboundBytesDelta > 0 || aliceDelta.inboundPacketsDelta > 0);
  const bobFlowing =
    (bobDelta.outboundBytesDelta > 0 || bobDelta.outboundPacketsDelta > 0) &&
    (bobDelta.inboundBytesDelta > 0 || bobDelta.inboundPacketsDelta > 0);

  if (!aliceFlowing || !bobFlowing) {
    // Leave voice
    await aliceRoom.disconnectButton.click();
    await bobRoom.disconnectButton.click();
    await alice.page.waitForTimeout(2_000);

    // Rejoin
    await doJoin();
  }

  // Expand voice workspace on both clients so the demand-driven screen
  // share request flow can fire (requires connectRemoteShares = true).
  // Click the "VIEW" badge that appears next to the active voice channel.
  const aliceView = alice.page.locator('app-rooms-side-panel')
    .getByRole('button', { name: /view/i })
    .first();
  const bobView = bob.page.locator('app-rooms-side-panel')
    .getByRole('button', { name: /view/i })
    .first();

  await expect(aliceView).toBeVisible({ timeout: 10_000 });
  await aliceView.click();
  await expect(alice.page.locator('app-voice-workspace')).toBeVisible({ timeout: 10_000 });

  await expect(bobView).toBeVisible({ timeout: 10_000 });
  await bobView.click();
  await expect(bob.page.locator('app-voice-workspace')).toBeVisible({ timeout: 10_000 });

  // Re-verify audio stats are present after workspace expansion (the VIEW
  // click can trigger renegotiation which briefly disrupts audio).
  await waitForAudioStatsPresent(alice.page, 20_000);
  await waitForAudioStatsPresent(bob.page, 20_000);
}

function expectFlowing(
  delta: { outboundBytesDelta: number; inboundBytesDelta: number; outboundPacketsDelta: number; inboundPacketsDelta: number },
  label: string
) {
  expect(
    delta.outboundBytesDelta > 0 || delta.outboundPacketsDelta > 0,
    `${label} should be sending`
  ).toBe(true);

  expect(
    delta.inboundBytesDelta > 0 || delta.inboundPacketsDelta > 0,
    `${label} should be receiving`
  ).toBe(true);
}

test.describe('Screen sharing', () => {
  test('single user screen share: video and audio flow to receiver, voice audio continues', async ({ createClient }) => {
    test.setTimeout(180_000);

    const alice = await createClient();
    const bob = await createClient();

    await installWebRTCTracking(alice.page);
    await installWebRTCTracking(bob.page);
    await installAutoResumeAudioContext(alice.page);
    await installAutoResumeAudioContext(bob.page);

    // Seed deterministic voice settings so noise reduction doesn't
    // swallow the fake audio tone.
    const voiceSettings = JSON.stringify({
      inputVolume: 100, outputVolume: 100, audioBitrate: 96,
      latencyProfile: 'balanced', includeSystemAudio: false,
      noiseReduction: false, screenShareQuality: 'balanced',
      askScreenShareQuality: false
    });

    await alice.page.addInitScript((settingsValue: string) => localStorage.setItem('metoyou_voice_settings', settingsValue), voiceSettings);
    await bob.page.addInitScript((settingsValue: string) => localStorage.setItem('metoyou_voice_settings', settingsValue), voiceSettings);

    alice.page.on('console', msg => console.log('[Alice]', msg.text()));
    bob.page.on('console', msg => console.log('[Bob]', msg.text()));

    // ── Setup: register, server, voice ────────────────────────────

    await test.step('Setup server and voice channel', async () => {
      await setupServerWithBothUsers(alice, bob);
      await joinVoiceTogether(alice, bob);
    });

    // ── Verify voice audio before screen share ────────────────────

    await test.step('Voice audio flows before screen share', async () => {
      const aliceDelta = await waitForAudioFlow(alice.page, 30_000);
      const bobDelta = await waitForAudioFlow(bob.page, 30_000);

      expectFlowing(aliceDelta, 'Alice voice');
      expectFlowing(bobDelta, 'Bob voice');
    });

    // ── Alice starts screen sharing ───────────────────────────────

    await test.step('Alice starts screen sharing', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      await aliceRoom.startScreenShare();

      // Screen share button should show active state (MonitorOff icon)
      await expect(aliceRoom.isScreenShareActive).toBeVisible({ timeout: 10_000 });
    });

    // ── Verify screen share video flows ───────────────────────────

    await test.step('Screen share video flows from Alice to Bob', async () => {
      // Screen share is unidirectional: Alice sends video, Bob receives it.
      const aliceVideo = await waitForOutboundVideoFlow(alice.page, 30_000);
      const bobVideo = await waitForInboundVideoFlow(bob.page, 30_000);

      if (aliceVideo.outboundBytesDelta === 0 || bobVideo.inboundBytesDelta === 0) {
        console.log('[Alice RTC]\n' + await dumpRtcDiagnostics(alice.page));
        console.log('[Bob RTC]\n' + await dumpRtcDiagnostics(bob.page));
      }

      expect(
        aliceVideo.outboundBytesDelta > 0 || aliceVideo.outboundPacketsDelta > 0,
        'Alice should be sending screen share video'
      ).toBe(true);

      expect(
        bobVideo.inboundBytesDelta > 0 || bobVideo.inboundPacketsDelta > 0,
        'Bob should be receiving screen share video'
      ).toBe(true);
    });

    // ── Verify voice audio continues during screen share ──────────

    await test.step('Voice audio continues during screen share', async () => {
      const aliceAudio = await waitForAudioFlow(alice.page, 20_000);
      const bobAudio = await waitForAudioFlow(bob.page, 20_000);

      expectFlowing(aliceAudio, 'Alice voice during screen share');
      expectFlowing(bobAudio, 'Bob voice during screen share');
    });

    // ── Bob can hear Alice talk while she screen shares ───────────

    await test.step('Bob receives audio from Alice during screen share', async () => {
      // Specifically check Bob is receiving audio (from Alice's voice)
      const bobAudio = await waitForAudioFlow(bob.page, 15_000);

      expect(
        bobAudio.inboundBytesDelta > 0,
        'Bob should receive voice audio while Alice screen shares'
      ).toBe(true);
    });

    // ── Alice stops screen sharing ────────────────────────────────

    await test.step('Alice stops screen sharing', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      await aliceRoom.stopScreenShare();

      // Active icon should disappear - regular Monitor icon shown instead
      await expect(
        aliceRoom.voiceControls.locator('button:has(ng-icon[name="lucideMonitor"])').first()
      ).toBeVisible({ timeout: 10_000 });
    });

    // ── Voice audio still works after screen share ends ───────────

    await test.step('Voice audio resumes normally after screen share stops', async () => {
      const aliceAudio = await waitForAudioFlow(alice.page, 20_000);
      const bobAudio = await waitForAudioFlow(bob.page, 20_000);

      expectFlowing(aliceAudio, 'Alice voice after screen share');
      expectFlowing(bobAudio, 'Bob voice after screen share');
    });
  });

  test('multiple users screen share simultaneously', async ({ createClient }) => {
    test.setTimeout(180_000);

    const alice = await createClient();
    const bob = await createClient();

    await installWebRTCTracking(alice.page);
    await installWebRTCTracking(bob.page);
    await installAutoResumeAudioContext(alice.page);
    await installAutoResumeAudioContext(bob.page);

    const voiceSettings = JSON.stringify({
      inputVolume: 100, outputVolume: 100, audioBitrate: 96,
      latencyProfile: 'balanced', includeSystemAudio: false,
      noiseReduction: false, screenShareQuality: 'balanced',
      askScreenShareQuality: false
    });

    await alice.page.addInitScript((settingsValue: string) => localStorage.setItem('metoyou_voice_settings', settingsValue), voiceSettings);
    await bob.page.addInitScript((settingsValue: string) => localStorage.setItem('metoyou_voice_settings', settingsValue), voiceSettings);

    alice.page.on('console', msg => console.log('[Alice]', msg.text()));
    bob.page.on('console', msg => console.log('[Bob]', msg.text()));

    await test.step('Setup server and voice channel', async () => {
      await setupServerWithBothUsers(alice, bob);
      await joinVoiceTogether(alice, bob);
    });

    // ── Both users start screen sharing ───────────────────────────

    await test.step('Alice starts screen sharing', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      await aliceRoom.startScreenShare();
      await expect(aliceRoom.isScreenShareActive).toBeVisible({ timeout: 10_000 });
    });

    await test.step('Bob starts screen sharing', async () => {
      const bobRoom = new ChatRoomPage(bob.page);

      await bobRoom.startScreenShare();
      await expect(bobRoom.isScreenShareActive).toBeVisible({ timeout: 10_000 });
    });

    // ── Verify video flows in both directions ─────────────────────

    await test.step('Video flows bidirectionally with both screen shares active', async () => {
      // Both sharing: each page sends and receives video
      const aliceVideo = await waitForVideoFlow(alice.page, 30_000);
      const bobVideo = await waitForVideoFlow(bob.page, 30_000);

      expectFlowing(aliceVideo, 'Alice screen share video');
      expectFlowing(bobVideo, 'Bob screen share video');
    });

    // ── Voice audio continues with dual screen shares ─────────────

    await test.step('Voice audio continues with both users screen sharing', async () => {
      const aliceAudio = await waitForAudioFlow(alice.page, 20_000);
      const bobAudio = await waitForAudioFlow(bob.page, 20_000);

      expectFlowing(aliceAudio, 'Alice voice during dual screen share');
      expectFlowing(bobAudio, 'Bob voice during dual screen share');
    });

    // ── Both stop screen sharing ──────────────────────────────────

    await test.step('Both users stop screen sharing', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);
      const bobRoom = new ChatRoomPage(bob.page);

      await aliceRoom.stopScreenShare();
      await expect(
        aliceRoom.voiceControls.locator('button:has(ng-icon[name="lucideMonitor"])').first()
      ).toBeVisible({ timeout: 10_000 });

      await bobRoom.stopScreenShare();
      await expect(
        bobRoom.voiceControls.locator('button:has(ng-icon[name="lucideMonitor"])').first()
      ).toBeVisible({ timeout: 10_000 });
    });
  });

  test('screen share connection stays stable for 10+ seconds', async ({ createClient }) => {
    test.setTimeout(180_000);

    const alice = await createClient();
    const bob = await createClient();

    await installWebRTCTracking(alice.page);
    await installWebRTCTracking(bob.page);
    await installAutoResumeAudioContext(alice.page);
    await installAutoResumeAudioContext(bob.page);

    const voiceSettings = JSON.stringify({
      inputVolume: 100, outputVolume: 100, audioBitrate: 96,
      latencyProfile: 'balanced', includeSystemAudio: false,
      noiseReduction: false, screenShareQuality: 'balanced',
      askScreenShareQuality: false
    });

    await alice.page.addInitScript((settingsValue: string) => localStorage.setItem('metoyou_voice_settings', settingsValue), voiceSettings);
    await bob.page.addInitScript((settingsValue: string) => localStorage.setItem('metoyou_voice_settings', settingsValue), voiceSettings);

    alice.page.on('console', msg => console.log('[Alice]', msg.text()));
    bob.page.on('console', msg => console.log('[Bob]', msg.text()));

    await test.step('Setup server and voice channel', async () => {
      await setupServerWithBothUsers(alice, bob);
      await joinVoiceTogether(alice, bob);
    });

    await test.step('Alice starts screen sharing', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      await aliceRoom.startScreenShare();
      await expect(aliceRoom.isScreenShareActive).toBeVisible({ timeout: 10_000 });

      // Wait for video pipeline to fully establish
      await waitForOutboundVideoFlow(alice.page, 30_000);
      await waitForInboundVideoFlow(bob.page, 30_000);
    });

    // ── Stability checkpoints at 0s, 5s, 10s ─────────────────────

    await test.step('Connection stays stable for 10+ seconds during screen share', async () => {
      for (const checkpoint of [
        0,
        5_000,
        5_000
      ]) {
        if (checkpoint > 0) {
          await alice.page.waitForTimeout(checkpoint);
        }

        const aliceConnected = await isPeerStillConnected(alice.page);
        const bobConnected = await isPeerStillConnected(bob.page);

        expect(aliceConnected, 'Alice should still be connected').toBe(true);
        expect(bobConnected, 'Bob should still be connected').toBe(true);
      }

      // After 10s - verify both video and audio still flowing
      const aliceVideo = await waitForOutboundVideoFlow(alice.page, 15_000);
      const bobVideo = await waitForInboundVideoFlow(bob.page, 15_000);

      expect(
        aliceVideo.outboundBytesDelta > 0,
        'Alice still sending screen share video after 10s'
      ).toBe(true);

      expect(
        bobVideo.inboundBytesDelta > 0,
        'Bob still receiving screen share video after 10s'
      ).toBe(true);

      const aliceAudio = await waitForAudioFlow(alice.page, 15_000);
      const bobAudio = await waitForAudioFlow(bob.page, 15_000);

      expectFlowing(aliceAudio, 'Alice voice after 10s screen share');
      expectFlowing(bobAudio, 'Bob voice after 10s screen share');
    });

    // ── Clean disconnect ──────────────────────────────────────────

    await test.step('Alice stops screen share and disconnects', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      await aliceRoom.stopScreenShare();
      await aliceRoom.disconnectButton.click();
      await expect(aliceRoom.disconnectButton).not.toBeVisible({ timeout: 10_000 });
    });
  });
});
