import { test, expect } from '../support/cross-app-fixtures';
import { mockAllAPIs } from '../support/cross-app-fixtures';
import { S, tid } from '../support/selectors';

/**
 * User Stories 17-30: Advanced Search & Filter Scenarios
 *
 * 17: User clears search inputs
 * 18: User swaps departure and arrival cities
 * 19: User selects date from calendar
 * 20: User searches with date range
 * 21: User views search history
 * 22: User searches from search history
 * 23: User searches with invalid city
 * 24: User searches with empty fields
 * 25: User searches with date before today
 * 26: User searches with same departure and arrival
 * 27: User searches with special characters
 * 28: User searches with Unicode characters
 * 29: User searches with very long city name
 * 30: User searches with rapid attempts
 */

test.describe('User Stories 17-30: Advanced Search & Filter Scenarios', () => {
  test.beforeEach(async ({ page, localePath }) => {
    await mockAllAPIs(page);
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 17: User clears search inputs
  // ─────────────────────────────────────────────────────────────────────────

  test('17.1: User clears departure city input', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    await departureInput.fill('Moscow');
    await page.waitForTimeout(500);

    const clearButton = departureInput
      .locator(
        'button[aria-label*="clear"], button[aria-label*="Clear"], .p-input-icon-right button, [data-testid*="clear"]',
      )
      .first();
    if ((await clearButton.count()) > 0) {
      await clearButton.click();
      await page.waitForTimeout(300);
      const value = await departureInput.inputValue();
      expect(value).toBe('');
    }
  });

  test('17.2: User clears arrival city input', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_ARRIVAL_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const arrivalInput = page.locator(tid(S.FILTER_ROUTE_ARRIVAL_INPUT, app));
    await arrivalInput.fill('Sochi');
    await page.waitForTimeout(500);

    const clearButton = arrivalInput
      .locator(
        'button[aria-label*="clear"], button[aria-label*="Clear"], .p-input-icon-right button, [data-testid*="clear"]',
      )
      .first();
    if ((await clearButton.count()) > 0) {
      await clearButton.click();
      await page.waitForTimeout(300);
      const value = await arrivalInput.inputValue();
      expect(value).toBe('');
    }
  });

  test('17.3: User clears flight number input', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const flightTab = page.locator(tid(S.FILTER_FLIGHT_TAB, app));
    const fallback = page.locator('[data-testid="flight-filter"]');
    const tabEl = (await flightTab.count()) > 0 ? flightTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_FLIGHT_NUMBER_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const flightInput = page.locator(tid(S.FILTER_FLIGHT_NUMBER_INPUT, app));
    await flightInput.fill('SU1234');
    await page.waitForTimeout(500);

    const clearButton = flightInput
      .locator(
        'button[aria-label*="clear"], button[aria-label*="Clear"], .p-input-icon-right button, [data-testid*="clear"]',
      )
      .first();
    if ((await clearButton.count()) > 0) {
      await clearButton.click();
      await page.waitForTimeout(300);
      const value = await flightInput.inputValue();
      expect(value).toBe('');
    }
  });

  test('17.4: User clears all search inputs at once', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    const arrivalInput = page.locator(tid(S.FILTER_ROUTE_ARRIVAL_INPUT, app));

    await departureInput.fill('Moscow');
    await arrivalInput.fill('Sochi');
    await page.waitForTimeout(500);

    const clearButtons = page.locator(
      'button[aria-label*="clear"], button[aria-label*="Clear"], .p-input-icon-right button, [data-testid*="clear"]',
    );
    const count = await clearButtons.count();
    if (count > 0) {
      for (let i = 0; i < Math.min(count, 5); i++) {
        await clearButtons.nth(i).click();
        await page.waitForTimeout(200);
      }
    }

    const depValue = await departureInput.inputValue();
    const arrValue = await arrivalInput.inputValue();
    expect(depValue).toBe('');
    expect(arrValue).toBe('');
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 18: User swaps departure and arrival cities
  // ─────────────────────────────────────────────────────────────────────────

  test('18.1: User swaps departure and arrival cities', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    const arrivalInput = page.locator(tid(S.FILTER_ROUTE_ARRIVAL_INPUT, app));
    const swapButton = page.locator(tid(S.FILTER_ROUTE_SWAP_BUTTON, app));

    await departureInput.fill('Moscow');
    await arrivalInput.fill('Sochi');
    await page.waitForTimeout(500);

    const depBefore = await departureInput.inputValue();
    const arrBefore = await arrivalInput.inputValue();

    if ((await swapButton.count()) > 0) {
      await swapButton.click();
      await page.waitForTimeout(500);

      const depAfter = await departureInput.inputValue();
      const arrAfter = await arrivalInput.inputValue();

      expect(depAfter).toBe(arrBefore);
      expect(arrAfter).toBe(depBefore);
    }
  });

  test('18.2: Swap button is visible and enabled', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const swapButton = page.locator(tid(S.FILTER_ROUTE_SWAP_BUTTON, app));
    await expect(swapButton).toBeVisible();
    await expect(swapButton).toBeEnabled();
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 19: User selects date from calendar
  // ─────────────────────────────────────────────────────────────────────────

  test('19.1: Calendar input is visible and clickable', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_CALENDAR, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const calendarInput = page.locator(tid(S.FILTER_ROUTE_CALENDAR, app));
    await expect(calendarInput).toBeVisible();
    await expect(calendarInput).toBeEnabled();
  });

  test('19.2: Calendar overlay opens on click', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_CALENDAR, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const calendarInput = page.locator(tid(S.FILTER_ROUTE_CALENDAR, app));
    await calendarInput.click();
    await page.waitForTimeout(500);

    const calendarOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    await expect(calendarOverlay.first()).toBeVisible({ timeout: 5000 });
  });

  test('19.3: User selects future date from calendar', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_CALENDAR, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const calendarInput = page.locator(tid(S.FILTER_ROUTE_CALENDAR, app));
    await calendarInput.click();
    await page.waitForTimeout(500);

    const calendarOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    await expect(calendarOverlay.first()).toBeVisible({ timeout: 5000 });

    const futureDate = calendarOverlay.locator(
      'td.p-datepicker-day:not(.p-disabled):not(.p-datepicker-today):nth-child(>7)',
    );
    const count = await futureDate.count();
    if (count > 0) {
      await futureDate.first().click();
      await page.waitForTimeout(300);
    }
  });

  test('19.4: Selected date displays in correct format', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_CALENDAR, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const calendarInput = page.locator(tid(S.FILTER_ROUTE_CALENDAR, app));
    await calendarInput.click();
    await page.waitForTimeout(500);

    const calendarOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    await expect(calendarOverlay.first()).toBeVisible({ timeout: 5000 });

    const todayCell = calendarOverlay.locator('td.p-datepicker-today');
    if ((await todayCell.count()) > 0) {
      await todayCell.first().click();
      await page.waitForTimeout(300);

      const inputValue = await calendarInput.inputValue();
      expect(inputValue.length).toBeGreaterThan(0);

      const dateRegex = /\d{1,2}[.\\/-]\d{1,2}[.\\/-]\d{2,4}/;
      expect(inputValue).toMatch(dateRegex);
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 20: User searches with date range
  // ─────────────────────────────────────────────────────────────────────────

  test('20.1: Schedule page has date range inputs', async ({ page, app, localePath }) => {
    await page.goto(localePath('schedule'));
    await page.waitForLoadState('networkidle');

    const outboundCalendar = page.locator(tid(S.SCHEDULE_CALENDAR, app));
    const returnCalendar = page.locator(tid(S.SCHEDULE_RETURN_CALENDAR, app));

    const outboundVisible = await outboundCalendar.count();
    const returnVisible = await returnCalendar.count();

    expect(outboundVisible).toBeGreaterThan(0);
    expect(returnVisible).toBeGreaterThan(0);
  });

  test('20.2: User selects outbound date', async ({ page, app, localePath }) => {
    await page.goto(localePath('schedule'));
    await page.waitForLoadState('networkidle');

    const outboundCalendar = page.locator(tid(S.SCHEDULE_CALENDAR, app));
    await outboundCalendar.click();
    await page.waitForTimeout(500);

    const calendarOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    await expect(calendarOverlay.first()).toBeVisible({ timeout: 5000 });

    const todayCell = calendarOverlay.locator('td.p-datepicker-today');
    if ((await todayCell.count()) > 0) {
      await todayCell.first().click();
      await page.waitForTimeout(300);
    }
  });

  test('20.3: User selects return date', async ({ page, app, localePath }) => {
    await page.goto(localePath('schedule'));
    await page.waitForLoadState('networkidle');

    const returnCalendar = page.locator(tid(S.SCHEDULE_RETURN_CALENDAR, app));
    await returnCalendar.click();
    await page.waitForTimeout(500);

    const calendarOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    await expect(calendarOverlay.first()).toBeVisible({ timeout: 5000 });

    const todayCell = calendarOverlay.locator('td.p-datepicker-today');
    if ((await todayCell.count()) > 0) {
      await todayCell.first().click();
      await page.waitForTimeout(300);
    }
  });

  test('20.4: Date range search executes successfully', async ({ page, app, localePath }) => {
    await page.goto(localePath('schedule'));
    await page.waitForLoadState('networkidle');

    const departureInput = page.locator(tid(S.SCHEDULE_DEPARTURE_INPUT, app));
    const arrivalInput = page.locator(tid(S.SCHEDULE_ARRIVAL_INPUT, app));
    const outboundCalendar = page.locator(tid(S.SCHEDULE_CALENDAR, app));
    const returnCalendar = page.locator(tid(S.SCHEDULE_RETURN_CALENDAR, app));
    const searchButton = page.locator(tid(S.SCHEDULE_SEARCH_BUTTON, app));

    await departureInput.fill('Moscow');
    await arrivalInput.fill('Sochi');
    await outboundCalendar.click();
    await page.waitForTimeout(500);

    const outboundOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    const todayCell = outboundOverlay.locator('td.p-datepicker-today');
    if ((await todayCell.count()) > 0) {
      await todayCell.first().click();
      await page.waitForTimeout(300);
    }

    await returnCalendar.click();
    await page.waitForTimeout(500);

    const returnOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    const futureCell = returnOverlay.locator('td.p-datepicker-day:not(.p-disabled):nth-child(>7)');
    const count = await futureCell.count();
    if (count > 0) {
      await futureCell.first().click();
      await page.waitForTimeout(300);
    }

    await searchButton.click();
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const results = page.locator(
      'schedule-result, .schedule-result, [data-testid*="schedule-flight"], .schedule__item',
    );
    const countResults = await results.count();
    expect(countResults).toBeGreaterThanOrEqual(0);
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 21: User views search history
  // ─────────────────────────────────────────────────────────────────────────

  test('21.1: Search history section exists on landing', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const historySection = page.locator(
      'search-history, [data-testid="landing-search-history"], .search-history, [class*="search-history"]',
    );
    const count = await historySection.count();
    expect(count).toBeGreaterThan(0);
  });

  test('21.2: Search history is empty by default', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const historySection = page.locator(
      'search-history, [data-testid="landing-search-history"], .search-history, [class*="search-history"]',
    );
    const historyItems = historySection.locator(
      '.history-item, [data-testid="landing-search-history-item"], .search-history__item',
    );
    const count = await historyItems.count();
    expect(count).toBeGreaterThanOrEqual(0);
  });

  test('21.3: Search history appears after performing search', async ({
    page,
    app,
    localePath,
  }) => {
    const today = formatToday();
    await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(2000);

    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const historyItems = page.locator(
      'search-history .history-item, [data-testid="landing-search-history-item"], .search-history__item',
    );
    const count = await historyItems.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 22: User searches from search history
  // ─────────────────────────────────────────────────────────────────────────

  test('22.1: Clicking history item re-executes search', async ({ page, app, localePath }) => {
    const today = formatToday();
    await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(2000);

    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const historyItems = page.locator(
      'search-history .history-item, [data-testid="landing-search-history-item"], .search-history__item',
    );
    const count = await historyItems.count();
    if (count > 0) {
      const urlBefore = page.url();
      await historyItems.first().click();
      await page.waitForTimeout(1000);
      const urlAfter = page.url();
      expect(urlAfter).not.toBe(urlBefore);
    }
  });

  test('22.2: History item URL matches search parameters', async ({ page, app, localePath }) => {
    const today = formatToday();
    await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(2000);

    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const historyItems = page.locator(
      'search-history .history-item, [data-testid="landing-search-history-item"], .search-history__item',
    );
    const count = await historyItems.count();
    if (count > 0) {
      await historyItems.first().click();
      await page.waitForTimeout(1000);

      const url = page.url();
      expect(url).toContain('departure');
      expect(url).toContain('MOW');
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 23: User searches with invalid city
  // ─────────────────────────────────────────────────────────────────────────

  test('23.1: Invalid city shows error message', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    await departureInput.fill('INVALIDCITYXYZ');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(1000);

    const errorMessages = page.locator(
      '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
    );
    const count = await errorMessages.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  test('23.2: Invalid city search does not return flights', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    await departureInput.fill('INVALIDCITYXYZ');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const flightResults = page.locator(
      'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
    );
    const count = await flightResults.count();
    expect(count).toBeGreaterThanOrEqual(0);
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 24: User searches with empty fields
  // ─────────────────────────────────────────────────────────────────────────

  test('24.1: Empty search shows validation error', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(500);

    const errorMessages = page.locator(
      '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
    );
    const count = await errorMessages.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  test('24.2: Empty search does not navigate away', async ({ page, app, localePath }) => {
    const urlBefore = page.url();
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(500);

    const urlAfter = page.url();
    expect(urlAfter).toBe(urlBefore);
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 25: User searches with date before today
  // ─────────────────────────────────────────────────────────────────────────

  test('25.1: Past date shows validation error', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_CALENDAR, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const calendarInput = page.locator(tid(S.FILTER_ROUTE_CALENDAR, app));
    await calendarInput.click();
    await page.waitForTimeout(500);

    const calendarOverlay = page.locator(
      '.p-calendar-panel, .p-datepicker, .ng-tns-c-date-picker, [role="dialog"]',
    );
    await expect(calendarOverlay.first()).toBeVisible({ timeout: 5000 });

    const pastDate = calendarOverlay.locator('td.p-datepicker-day.p-disabled');
    const count = await pastDate.count();
    if (count > 0) {
      await pastDate.first().click();
      await page.waitForTimeout(300);

      const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
      await searchButton.click();
      await page.waitForTimeout(500);

      const errorMessages = page.locator(
        '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
      );
      const errorCount = await errorMessages.count();
      if (errorCount > 0) {
        expect(errorCount).toBeGreaterThanOrEqual(1);
      }
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 26: User searches with same departure and arrival
  // ─────────────────────────────────────────────────────────────────────────

  test('26.1: Same departure and arrival shows validation error', async ({
    page,
    app,
    localePath,
  }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    const arrivalInput = page.locator(tid(S.FILTER_ROUTE_ARRIVAL_INPUT, app));

    await departureInput.fill('Moscow');
    await arrivalInput.fill('Moscow');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(500);

    const errorMessages = page.locator(
      '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
    );
    const count = await errorMessages.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 27: User searches with special characters
  // ─────────────────────────────────────────────────────────────────────────

  test('27.1: Special characters in flight number', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const flightTab = page.locator(tid(S.FILTER_FLIGHT_TAB, app));
    const fallback = page.locator('[data-testid="flight-filter"]');
    const tabEl = (await flightTab.count()) > 0 ? flightTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_FLIGHT_NUMBER_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const flightInput = page.locator(tid(S.FILTER_FLIGHT_NUMBER_INPUT, app));
    await flightInput.fill('SU@#$%123');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_FLIGHT_NUMBER_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(1000);

    const errorMessages = page.locator(
      '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
    );
    const count = await errorMessages.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  test('27.2: Special characters in city name', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    await departureInput.fill('Moscow@#$%');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(1000);

    const errorMessages = page.locator(
      '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
    );
    const count = await errorMessages.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 28: User searches with Unicode characters
  // ─────────────────────────────────────────────────────────────────────────

  test('28.1: Unicode characters in city name', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    await departureInput.fill('Москва');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const results = page.locator(
      'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
    );
    const count = await results.count();
    expect(count).toBeGreaterThanOrEqual(0);
  });

  test('28.2: Unicode characters in flight number', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const flightTab = page.locator(tid(S.FILTER_FLIGHT_TAB, app));
    const fallback = page.locator('[data-testid="flight-filter"]');
    const tabEl = (await flightTab.count()) > 0 ? flightTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_FLIGHT_NUMBER_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const flightInput = page.locator(tid(S.FILTER_FLIGHT_NUMBER_INPUT, app));
    await flightInput.fill('СУ1234');
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_FLIGHT_NUMBER_SEARCH, app));
    await searchButton.click();
    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(1000);

    const results = page.locator(
      'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
    );
    const count = await results.count();
    expect(count).toBeGreaterThanOrEqual(0);
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 29: User searches with very long city name
  // ─────────────────────────────────────────────────────────────────────────

  test('29.1: Very long city name is accepted', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    const longName = 'Москва' + ' '.repeat(100) + 'Moscow';
    await departureInput.fill(longName);
    await page.waitForTimeout(500);

    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    await searchButton.click();
    await page.waitForTimeout(1000);

    const errorMessages = page.locator(
      '.p-error, .error-message, [role="alert"], .ng-invalid.ng-dirty',
    );
    const count = await errorMessages.count();
    if (count > 0) {
      expect(count).toBeGreaterThanOrEqual(1);
    }
  });

  // ─────────────────────────────────────────────────────────────────────────
  // Story 30: User searches with rapid attempts
  // ─────────────────────────────────────────────────────────────────────────

  test('30.1: Rapid searches do not crash the app', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));

    for (let i = 0; i < 5; i++) {
      await departureInput.fill(`City${i}`);
      await page.waitForTimeout(100);
      await searchButton.click();
      await page.waitForTimeout(200);
    }

    const consoleErrors: string[] = [];
    page.on('console', (msg) => {
      if (msg.type() === 'error') {
        consoleErrors.push(msg.text());
      }
    });

    await page.waitForTimeout(1000);
    expect(consoleErrors.length).toBeLessThanOrEqual(0);
  });

  test('30.2: Rapid searches show loading state', async ({ page, app, localePath }) => {
    await page.goto(localePath('onlineboard'));
    await page.waitForLoadState('networkidle');

    const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
    const fallback = page.locator('[data-testid="route-filter"]');
    const tabEl = (await routeTab.count()) > 0 ? routeTab : fallback;

    const isExpanded = await page
      .locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app))
      .isVisible()
      .catch(() => false);

    if (!isExpanded) {
      const headerLink = tabEl.locator('.p-accordion-header-link, .p-accordion-header a').first();
      if ((await headerLink.count()) > 0) {
        await headerLink.click();
      } else {
        await tabEl.click();
      }
      await page.waitForTimeout(500);
    }

    const departureInput = page.locator(tid(S.FILTER_ROUTE_DEPARTURE_INPUT, app));
    const searchButton = page.locator(tid(S.FILTER_ROUTE_SEARCH, app));
    const loader = page.locator(tid(S.BOARD_LOADER, app));

    for (let i = 0; i < 3; i++) {
      await departureInput.fill(`City${i}`);
      await page.waitForTimeout(100);
      await searchButton.click();
      await page.waitForTimeout(200);

      const loaderVisible = await loader.count();
      if (loaderVisible > 0) {
        await loader.first().waitFor({ state: 'hidden', timeout: 10000 });
      }
    }
  });
});

function formatToday(): string {
  const d = new Date();
  return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
}
