import { type Page } from '@playwright/test';
import {
  test,
  expect,
  type Client
} from '../../fixtures/multi-client';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
import { ChatMessagesPage } from '../../pages/chat-messages.page';
import { disableLastViewedChatResume } from '../../helpers/seed-test-endpoint';

test.describe('Direct message flow', () => {
  test.describe.configure({ timeout: 180_000 });

  test('opens a DM from a user card and queues messages while offline', async ({ createClient }) => {
    const scenario = await createDmScenario(createClient);
    const offlineMessage = `Offline DM ${uniqueName('msg')}`;

    await test.step('Alice opens Bob from the room user list', async () => {
      const bobUserCard = scenario.alice.page.locator('[data-testid^="room-user-card-"]', { hasText: 'Bob' }).first();

      await expect(bobUserCard).toBeVisible({ timeout: 20_000 });
      await bobUserCard.getByRole('button', { name: 'Message Bob' }).click();
      await expect(scenario.alice.page).toHaveURL(/\/dm\//, { timeout: 15_000 });
      await expect(scenario.alice.page.getByRole('heading', { name: 'Bob' })).toBeVisible({ timeout: 10_000 });
    });

    await test.step('Offline send persists locally as queued', async () => {
      await scenario.alice.page.evaluate(() => window.simulateOffline?.());
      await scenario.alice.page.getByTestId('dm-input').fill(offlineMessage);
      await scenario.alice.page.getByTestId('dm-input').press('Enter');

      await expect(scenario.alice.page.locator('app-dm-chat').getByText(offlineMessage)).toBeVisible({ timeout: 10_000 });
      await expect(scenario.alice.page.getByTestId('message-status').last()).toContainText('QUEUED');
    });
  });

  test('delivers a live DM to the recipient conversation', async ({ createClient }) => {
    const scenario = await createDmScenario(createClient);
    const liveMessage = `Live DM ${uniqueName('msg')}`;

    await openDmFromRoomUserCard(scenario.alice.page, 'Bob');
    await scenario.alice.page.getByTestId('dm-input').fill(liveMessage);
    await scenario.alice.page.getByTestId('dm-input').press('Enter');

    await openDmFromRoomUserCard(scenario.bob.page, 'Alice');
    await expect(scenario.bob.page.locator('app-dm-chat').getByText(liveMessage)).toBeVisible({ timeout: 20_000 });
  });

  test('shows friend and message actions on the search people list', async ({ createClient }) => {
    const scenario = await createDmScenario(createClient);

    await disableLastViewedChatResume(scenario.alice.page);
    await scenario.alice.page.goto('/people', { waitUntil: 'domcontentloaded' });
    await expect(scenario.alice.page).toHaveURL(/\/people/, { timeout: 20_000 });
    await expect(scenario.alice.page.locator('app-find-people')).toBeVisible({ timeout: 20_000 });
    await expect(scenario.alice.page.locator('app-user-search-list')).toBeVisible({ timeout: 20_000 });
    const bobPeopleCard = scenario.alice.page
      .locator('app-user-search-list [data-testid$="-' + scenario.bobUserId + '"]', { hasText: 'Bob' })
      .first();

    await expect(bobPeopleCard).toBeVisible({ timeout: 15_000 });
    const friendButton = bobPeopleCard.locator(`[data-testid="friend-button-${scenario.bobUserId}"]`);
    const messageButton = bobPeopleCard.getByRole('button', { name: 'Message Bob' });

    await expect(friendButton).toBeAttached({ timeout: 15_000 });
    await expect(messageButton).toBeAttached({ timeout: 15_000 });
  });
});

interface DmScenario {
  alice: Client;
  bob: Client;
  bobUserId: string;
  aliceSearch: ServerSearchPage;
}

async function createDmScenario(createClient: () => Promise<Client>): Promise<DmScenario> {
  const suffix = uniqueName('dm');
  const serverName = `DM Server ${suffix}`;
  const alice = await createClient();
  const bob = await createClient();

  await registerUser(alice.page, `alice_${suffix}`, 'Alice');
  await registerUser(bob.page, `bob_${suffix}`, 'Bob');

  const aliceSearch = new ServerSearchPage(alice.page);

  await aliceSearch.createServer(serverName, { description: 'E2E direct message discovery server' });
  await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });
  await new ChatMessagesPage(alice.page).waitForReady();

  const bobSearch = new ServerSearchPage(bob.page);

  await bobSearch.joinServerFromSearch(serverName);
  await expect(bob.page).toHaveURL(/\/room\//, { timeout: 15_000 });
  await new ChatMessagesPage(bob.page).waitForReady();
  const bobRoomCard = alice.page.locator('[data-testid^="room-user-card-"]', { hasText: 'Bob' }).first();

  await expect(bobRoomCard).toBeVisible({ timeout: 20_000 });

  const bobUserCardTestId = await bobRoomCard.getAttribute('data-testid');
  const bobUserId = bobUserCardTestId?.replace('room-user-card-', '');

  if (!bobUserId) {
    throw new Error('Expected Bob room user card to expose a stable test id.');
  }

  return {
    alice,
    bob,
    bobUserId,
    aliceSearch
  };
}

async function registerUser(page: Page, username: string, displayName: string): Promise<void> {
  const registerPage = new RegisterPage(page);

  await registerPage.goto();
  await registerPage.register(username, displayName, 'TestPass123!');
  await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
}

async function openDmFromRoomUserCard(page: Page, displayName: string): Promise<void> {
  const userCard = page.locator('[data-testid^="room-user-card-"]', { hasText: displayName }).first();

  await expect(userCard).toBeVisible({ timeout: 20_000 });
  await userCard.getByRole('button', { name: `Message ${displayName}` }).click();
  await expect(page).toHaveURL(/\/dm\//, { timeout: 15_000 });
  await expect(page.getByRole('heading', { name: displayName })).toBeVisible({ timeout: 10_000 });
}

function uniqueName(prefix: string): string {
  return `${prefix}-${Date.now()}-${Math.random().toString(36)
    .slice(2, 8)}`;
}
