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

/**
 * Full user journey: register -> create server -> join -> voice -> verify audio
 * for 10+ seconds of stable connectivity.
 *
 * Uses two independent browser contexts (Alice & Bob) to simulate real
 * multi-user WebRTC voice chat.
 */

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

test.describe('Full user journey: register -> server -> voice chat', () => {
  test('two users register, create server, join voice, and stay connected 10+ seconds with audio', async ({ createClient }) => {
    test.setTimeout(180_000); // 3 min - covers registration, server creation, voice establishment, and 10s stability check

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

    // Install WebRTC tracking before any navigation
    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);

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

    // ── Step 1: Register both users ──────────────────────────────────

    await test.step('Alice registers an account', async () => {
      const registerPage = new RegisterPage(alice.page);

      await registerPage.goto();
      await expect(registerPage.submitButton).toBeVisible();
      await registerPage.register(ALICE.username, ALICE.displayName, ALICE.password);

      // After registration, app should navigate to /search
      await expect(alice.page).toHaveURL(/\/search/, { timeout: 15_000 });
    });

    await test.step('Bob registers an account', async () => {
      const registerPage = new RegisterPage(bob.page);

      await registerPage.goto();
      await expect(registerPage.submitButton).toBeVisible();
      await registerPage.register(BOB.username, BOB.displayName, BOB.password);

      await expect(bob.page).toHaveURL(/\/search/, { timeout: 15_000 });
    });

    // ── Step 2: Alice creates a server ───────────────────────────────

    await test.step('Alice creates a new server', async () => {
      const searchPage = new ServerSearchPage(alice.page);

      await searchPage.createServer(SERVER_NAME, {
        description: 'E2E test server for voice testing'
      });

      // After server creation, app navigates to the room
      await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });
    });

    // ── Step 3: Bob joins the server ─────────────────────────────────

    await test.step('Bob finds and joins the server', async () => {
      const searchPage = new ServerSearchPage(bob.page);

      await searchPage.joinServerFromSearch(SERVER_NAME);

      // Bob should be in the room now
      await expect(bob.page).toHaveURL(/\/room\//, { timeout: 15_000 });
    });

    // ── Step 4: Create a voice channel (if one doesn't exist) ────────

    await test.step('Alice ensures a voice channel is available', async () => {
      const chatRoom = new ChatRoomPage(alice.page);
      const existingVoiceChannel = alice.page.locator('app-rooms-side-panel')
        .getByRole('button', { name: VOICE_CHANNEL, exact: true });
      const voiceChannelExists = await existingVoiceChannel.count() > 0;

      if (!voiceChannelExists) {
        // Click "Create Voice Channel" plus button
        await chatRoom.openCreateVoiceChannelDialog();
        await chatRoom.createChannel(VOICE_CHANNEL);

        // Wait for the channel to appear
        await expect(existingVoiceChannel).toBeVisible({ timeout: 10_000 });
      }
    });

    // ── Step 5: Both users join the voice channel ────────────────────

    await test.step('Alice joins the voice channel', async () => {
      const chatRoom = new ChatRoomPage(alice.page);

      await chatRoom.joinVoiceChannel(VOICE_CHANNEL);

      // Voice controls should appear (indicates voice is connected)
      await expect(alice.page.locator('app-voice-controls')).toBeVisible({ timeout: 15_000 });
    });

    await test.step('Bob joins the voice channel', async () => {
      const chatRoom = new ChatRoomPage(bob.page);

      await chatRoom.joinVoiceChannel(VOICE_CHANNEL);

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

    // ── Step 6: Verify WebRTC connection establishes ─────────────────

    await test.step('WebRTC peer connection reaches "connected" state', async () => {
      await waitForPeerConnected(alice.page, 30_000);
      await waitForPeerConnected(bob.page, 30_000);

      // Wait for audio RTP pipeline to appear before measuring deltas -
      // renegotiation after initial connect can temporarily remove stats.
      await waitForAudioStatsPresent(alice.page, 20_000);
      await waitForAudioStatsPresent(bob.page, 20_000);
    });

    // ── Step 7: Verify audio is flowing in both directions ───────────

    await test.step('Audio packets are flowing between Alice and Bob', async () => {
      // 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.
      let aliceDelta = await waitForAudioFlow(alice.page, 15_000);
      let bobDelta = await waitForAudioFlow(bob.page, 15_000);

      const isFlowing = (delta: typeof aliceDelta) =>
        (delta.outboundBytesDelta > 0 || delta.outboundPacketsDelta > 0) &&
        (delta.inboundBytesDelta > 0 || delta.inboundPacketsDelta > 0);

      if (!isFlowing(aliceDelta) || !isFlowing(bobDelta)) {
        const aliceRoom = new ChatRoomPage(alice.page);
        const bobRoom = new ChatRoomPage(bob.page);

        await aliceRoom.disconnectButton.click();
        await bobRoom.disconnectButton.click();
        await alice.page.waitForTimeout(2_000);

        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 });

        await waitForPeerConnected(alice.page, 30_000);
        await waitForPeerConnected(bob.page, 30_000);
        await waitForAudioStatsPresent(alice.page, 20_000);
        await waitForAudioStatsPresent(bob.page, 20_000);

        aliceDelta = await waitForAudioFlow(alice.page, 30_000);
        bobDelta = await waitForAudioFlow(bob.page, 30_000);
      }

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

      expectAudioFlow(aliceDelta, 'Alice');
      expectAudioFlow(bobDelta, 'Bob');
    });

    // ── Step 8: Verify UI states are correct ─────────────────────────

    await test.step('Voice UI shows correct state for both users', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);
      const bobRoom = new ChatRoomPage(bob.page);

      // Both should see voice controls with "Connected" status
      await expect(alice.page.locator('app-voice-controls')).toBeVisible();
      await expect(bob.page.locator('app-voice-controls')).toBeVisible();

      // Both should see the voice workspace or at least voice users listed
      // Check that both users appear in the voice channel user list
      const aliceSeesBob = aliceRoom.channelsSidePanel.getByText(BOB.displayName).first();
      const bobSeesAlice = bobRoom.channelsSidePanel.getByText(ALICE.displayName).first();

      await expect(aliceSeesBob).toBeVisible({ timeout: 10_000 });
      await expect(bobSeesAlice).toBeVisible({ timeout: 10_000 });
    });

    // ── Step 9: Stay connected for 10+ seconds, verify stability ─────

    await test.step('Connection remains stable for 10+ seconds', async () => {
      // Check connectivity at 0s, 5s, and 10s intervals
      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 total, verify audio is still flowing
      const aliceDelta = await waitForAudioFlow(alice.page, 15_000);
      const bobDelta = await waitForAudioFlow(bob.page, 15_000);

      expectAudioFlow(aliceDelta, 'Alice after 10s');
      expectAudioFlow(bobDelta, 'Bob after 10s');
    });

    // ── Step 10: Verify mute/unmute works correctly ──────────────────

    await test.step('Mute toggle works correctly', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      // Alice mutes - click the first button in voice controls (mute button)
      await aliceRoom.muteButton.click();

      // After muting, Alice's outbound audio should stop increasing
      // When muted, bytesSent may still show small comfort noise or zero growth
      // The key assertion is that Bob's inbound for Alice's stream stops or reduces
      await getAudioStatsDelta(alice.page, 2_000);

      // Alice unmutes
      await aliceRoom.muteButton.click();

      // After unmuting, outbound should resume
      const unmutedDelta = await waitForAudioFlow(alice.page, 15_000);

      expectAudioFlow(unmutedDelta, 'Alice after unmuting');
    });

    // ── Step 11: Clean disconnect ────────────────────────────────────

    await test.step('Alice disconnects from voice', async () => {
      const aliceRoom = new ChatRoomPage(alice.page);

      // Click the disconnect/hang-up button
      await aliceRoom.disconnectButton.click();

      // Connected controls should collapse for Alice after disconnect
      await expect(aliceRoom.disconnectButton).not.toBeVisible({ timeout: 10_000 });
    });
  });
});

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

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