import { test, expect, nudgeEchoBot } from '../helpers/meshTrafficTest';
import { createChannel, getChannels, getMessages } from '../helpers/api';

/**
 * These tests wait for real incoming messages from the mesh network.
 * They require a radio attached and other nodes actively transmitting.
 * Timeout is 3 minutes to allow for intermittent traffic.
 */

const CHANNELS = [
  '#flightless', '#bot', '#snoco', '#skagit', '#edmonds', '#bachelorette',
  '#emergency', '#furry', '#public', '#puppy', '#foobar', '#capitolhill',
  '#hamradio', '#icewatch', '#saucefamily', '#scvsar', '#startrek', '#metalmusic',
  '#seattle', '#vanbot', '#bot-van', '#lynden', '#bham', '#sipesbot', '#psrg',
  '#testing', '#olybot', '#test', '#ve7rva', '#wardrive', '#kitsap', '#tacoma',
  '#rats', '#pdx', '#olympia', '#bot2', '#transit', '#salishmesh', '#meshwar',
  '#cats', '#jokes', '#decode', '#whatcom', '#bot-oly', '#sports', '#weather',
  '#wasma', '#ravenna', '#northbend', '#dsa', '#oly-bot', '#grove', '#cars',
  '#bellingham', '#baseball', '#mariners', '#eugene', '#victoria', '#vimesh',
  '#bot-pdx', '#chinese', '#miro', '#poop', '#papa', '#uw', '#renton',
  '#general', '#bellevue', '#eastside', '#bit', '#dev', '#farts', '#protest',
  '#gmrs', '#pri', '#boob', '#baga', '#fun', '#w7dk', '#wedgwood', '#bots',
  '#sounders', '#steelhead', '#uetfwf', '#ballard', '#at', '#1234567', '#funny',
  '#abbytest', '#abird', '#afterparty', '#arborheights', '#atheist', '#auburn',
  '#bbs', '#blog', '#bottest', '#cascadiamesh', '#chat', '#checkcheck',
  '#civicmesh', '#columbiacity', '#dad', '#dmaspace', '#droptable', '#duvall',
  '#dx', '#emcomm', '#finnhill', '#foxden', '#freebsd', '#greenwood', '#howlbot',
  '#idahomesh', '#junk', '#kraken', '#kremwerk', '#maplemesh', '#meshcore',
  '#meshmonday', '#methow', '#minecraft', '#newwestminster', '#northvan',
  '#ominous', '#pagan', '#party', '#place', '#pokemon', '#portland', '#rave',
  '#raving', '#rftest', '#richmond', '#rolston', '#salishtest', '#saved',
  '#seahawks', '#sipebot', '#slumbermesh', '#snoqualmie', '#southisland',
  '#sydney', '#tacobot', '#tdeck', '#trans', '#ubc', '#underground', '#van-bot',
  '#vancouver', '#vashon', '#wardriving', '#wormhole', '#yelling', '#zork',
];

// 3 minute timeout for waiting on mesh traffic
test.describe('Incoming mesh messages', () => {
  test.setTimeout(180_000);

  test.beforeAll(async () => {
    // Ensure all channels exist — create any that are missing
    const existing = await getChannels();
    const existingNames = new Set(existing.map((c) => c.name));

    for (const channel of CHANNELS) {
      if (!existingNames.has(channel)) {
        try {
          await createChannel(channel);
        } catch {
          // May already exist from a concurrent creation, ignore
        }
      }
    }
  });

  test('receive an incoming message in any channel', { tag: '@mesh-traffic' }, async ({ page }) => {
    // Nudge echo bot on #flightless — may generate an incoming packet quickly
    await nudgeEchoBot();

    await page.goto('/');
    await expect(page.getByRole('status', { name: 'Radio OK' })).toBeVisible();

    // Record existing message counts per channel so we can detect new ones
    const channels = await getChannels();
    const baselineCounts = new Map<string, number>();
    for (const ch of channels) {
      const msgs = await getMessages({ type: 'CHAN', conversation_key: ch.key, limit: 1 });
      baselineCounts.set(ch.key, msgs.length > 0 ? msgs[0].id : 0);
    }

    // Poll for a new incoming message across all channels
    let foundChannel: string | null = null;
    let foundMessageText: string | null = null;

    await expect(async () => {
      for (const ch of channels) {
        const msgs = await getMessages({
          type: 'CHAN',
          conversation_key: ch.key,
          limit: 5,
        });
        const baseline = baselineCounts.get(ch.key) ?? 0;
        const newIncoming = msgs.find((m) => m.id > baseline && !m.outgoing);
        if (newIncoming) {
          foundChannel = ch.name;
          foundMessageText = newIncoming.text;
          return;
        }
      }
      throw new Error('No new incoming messages yet');
    }).toPass({ intervals: [5_000], timeout: 160_000 });

    // Navigate to the channel that received a message
    console.log(`Received message in ${foundChannel}: "${foundMessageText}"`);
    await page.getByText(foundChannel!, { exact: true }).first().click();

    // Verify the message text is visible in the message list area (not sidebar)
    const messageArea = page.locator('.break-words');
    const messageContent = foundMessageText!.includes(': ')
      ? foundMessageText!.split(': ').slice(1).join(': ')
      : foundMessageText!;
    await expect(messageArea.getByText(messageContent, { exact: false }).first()).toBeVisible({
      timeout: 15_000,
    });
  });

  test('incoming message with path shows hop badge and path modal', { tag: '@mesh-traffic' }, async ({ page }) => {
    // Nudge echo bot on #flightless — may generate an incoming packet quickly
    await nudgeEchoBot();

    await page.goto('/');
    await expect(page.getByRole('status', { name: 'Radio OK' })).toBeVisible();

    // Record baselines
    const channels = await getChannels();
    const baselineCounts = new Map<string, number>();
    for (const ch of channels) {
      const msgs = await getMessages({ type: 'CHAN', conversation_key: ch.key, limit: 1 });
      baselineCounts.set(ch.key, msgs.length > 0 ? msgs[0].id : 0);
    }

    // Wait for any incoming message that has path data
    let foundChannel: string | null = null;

    await expect(async () => {
      for (const ch of channels) {
        const msgs = await getMessages({
          type: 'CHAN',
          conversation_key: ch.key,
          limit: 10,
        });
        const baseline = baselineCounts.get(ch.key) ?? 0;
        const withPath = msgs.find(
          (m) => m.id > baseline && !m.outgoing && m.paths && m.paths.length > 0
        );
        if (withPath) {
          foundChannel = ch.name;
          return;
        }
      }
      throw new Error('No new incoming messages with path data yet');
    }).toPass({ intervals: [5_000], timeout: 160_000 });

    console.log(`Found message with path in ${foundChannel}`);

    // Navigate to the channel that received a message with path data
    await page.getByText(foundChannel!, { exact: true }).first().click();

    // Find any hop badge on the page — they all have title="View message path"
    // We don't care which specific message; just that a path badge exists and works.
    const badge = page.getByTitle('View message path').first();
    await expect(badge).toBeVisible({ timeout: 15_000 });

    // The badge text should match the pattern: (d), (1), (d/1/3), etc.
    const badgeText = await badge.textContent();
    console.log(`Badge text: ${badgeText}`);
    expect(badgeText).toMatch(/^\([d\d]+(\/[d\d]+)*\)$/);

    // Click the badge to open the path modal
    await badge.click();

    const modal = page.getByRole('dialog');
    await expect(modal).toBeVisible();

    // Verify the modal has the basic structural elements every path modal should have
    await expect(modal.getByText('Sender:').first()).toBeVisible();
    await expect(modal.getByText('Receiver (me):').first()).toBeVisible();

    // Title should be either "Message Path" (single) or "Message Paths (N)" (multiple)
    const titleEl = modal.locator('h2, [class*="DialogTitle"]').first();
    const titleText = await titleEl.textContent();
    console.log(`Modal title: ${titleText}`);
    expect(titleText).toMatch(/^Message Paths?(\s+\(\d+\))?$/);

    // Close the modal
    await modal.getByRole('button', { name: 'Close', exact: true }).first().click();
    await expect(modal).not.toBeVisible();
  });
});
