import { test, expect } from '@playwright/test';

// Entity section tests verify generated content is properly served

// Helper to get random items from array
function getRandomItems<T>(array: T[], count: number): T[] {
  const shuffled = [...array].sort(() => 0.5 - Math.random());
  return shuffled.slice(0, Math.min(count, array.length));
}

// Helper to convert full URL to relative path
function toRelativePath(url: string): string {
  try {
    const parsed = new URL(url);
    return parsed.pathname;
  } catch {
    return url;
  }
}

// Wait timeout for async data loading (GraphQL browseEntities)
const DATA_LOAD_TIMEOUT = 15000;

const hasAuthCredentials = !!process.env.PLAYWRIGHT_AUTH_USER;

test.describe('us.elect.info entity sections', () => {

  test('candidates section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/candidates/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Candidates/i);

    // Wait for GraphQL browseEntities to populate results
    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('committees section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/committees/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Committees/i);

    // Wait for GraphQL browseEntities to populate results
    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('parties section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/parties/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Parties/i);

    // Wait for GraphQL browseEntities to populate results
    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('offices section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/offices/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Offices/i);

    // Wait for GraphQL browseEntities to populate results
    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('districts section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/districts/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Districts/i);

    // Map view is default — verify Leaflet map renders with GeoJSON state shapes
    await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    const shapes = await page.locator('#map .leaflet-interactive').all();
    expect(shapes.length).toBeGreaterThan(0);

    // Switch to List view to test state selector and district results
    await page.locator('.toggle-btn[data-view="list"]').click();

    // Districts list view loads state options from GraphQL, then requires selection
    const stateSelect = page.locator('#district-state-select');
    await expect(stateSelect).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    // Wait for state options to populate from GraphQL (more than just the placeholder)
    await page.waitForFunction(() => {
      const select = document.querySelector('#district-state-select') as HTMLSelectElement;
      return select && select.options.length > 1;
    }, { timeout: DATA_LOAD_TIMEOUT });

    await stateSelect.selectOption('CA');

    // Wait for district results to load
    await expect(page.locator('.district-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.district-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('states section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/states/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/States/i);

    // Map view is default — verify Leaflet map renders with GeoJSON state shapes
    await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    const shapes = await page.locator('#map .leaflet-interactive').all();
    expect(shapes.length).toBeGreaterThan(0);

    // Switch to Grid view to test state chips and detail navigation
    await page.locator('.toggle-btn[data-view="grid"]').click();

    await expect(page.locator('.state-chip').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.state-chip').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('elections section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/elections/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Election/i);

    // Wait for GraphQL browseEntities to populate results
    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });
});

test.describe('office profile - dynamic candidates', () => {
  // President of the United States office page
  const PRESIDENT_PATH = '/offices/684f6e2d-7105-932b-8b7f-94016580f727/';

  test('office profile loads candidates filtered to office', async ({ page }) => {
    // Intercept the browseEntities GraphQL request to verify filter is applied
    const responsePromise = page.waitForResponse(resp => {
      if (!resp.url().includes('/api/graphql')) return false;
      if (resp.request().method() !== 'POST') return false;
      const body = resp.request().postData() || '';
      return body.includes('browseEntities');
    }, { timeout: DATA_LOAD_TIMEOUT });

    await page.goto(PRESIDENT_PATH);
    const response = await responsePromise;

    // Verify the request includes the office filter
    const requestBody = JSON.parse(response.request().postData() || '{}');
    expect(requestBody.variables?.filter).toContain('office:P');

    // Verify the response has results
    const json = await response.json();
    const results = json.data?.browseEntities?.results || [];
    expect(results.length).toBeGreaterThan(0);
  });

  test('office profile candidates sorted by total contributions descending', async ({ page }) => {
    const responsePromise = page.waitForResponse(resp => {
      if (!resp.url().includes('/api/graphql')) return false;
      if (resp.request().method() !== 'POST') return false;
      const body = resp.request().postData() || '';
      return body.includes('browseEntities');
    }, { timeout: DATA_LOAD_TIMEOUT });

    await page.goto(PRESIDENT_PATH);
    const response = await responsePromise;

    const json = await response.json();
    const results = json.data?.browseEntities?.results || [];
    expect(results.length).toBeGreaterThan(1);

    // Extract total_contributions from metadata to verify sort order
    const contributions = results.map((r: { metadata?: string }) => {
      try {
        const meta = JSON.parse(r.metadata || '{}');
        return meta.total_contributions || 0;
      } catch { return 0; }
    });

    for (let i = 1; i < contributions.length; i++) {
      expect(contributions[i - 1]).toBeGreaterThanOrEqual(contributions[i]);
    }
  });

  test('office profile renders candidate results in UI', async ({ page }) => {
    await page.goto(PRESIDENT_PATH);

    // Wait for candidate results to render
    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const results = await page.locator('.entity-search-result').all();
    expect(results.length).toBeGreaterThan(0);

    // First result should be a well-known presidential candidate with high contributions
    const firstResultText = await results[0].textContent();
    expect(firstResultText).toMatch(/\$/); // Should show dollar amount
  });
});

test.describe('state profile - dynamic candidates', () => {
  const TEXAS_PATH = '/states/tx/';

  test('state profile loads candidates filtered to state', async ({ page }) => {
    const responsePromise = page.waitForResponse(resp => {
      if (!resp.url().includes('/api/graphql')) return false;
      if (resp.request().method() !== 'POST') return false;
      const body = resp.request().postData() || '';
      return body.includes('browseEntities');
    }, { timeout: DATA_LOAD_TIMEOUT });

    await page.goto(TEXAS_PATH);
    const response = await responsePromise;

    const requestBody = JSON.parse(response.request().postData() || '{}');
    expect(requestBody.variables?.filter).toContain('office_state:TX');

    const json = await response.json();
    const results = json.data?.browseEntities?.results || [];
    expect(results.length).toBeGreaterThan(0);
  });

  test('state profile candidates sorted by total contributions descending', async ({ page }) => {
    const responsePromise = page.waitForResponse(resp => {
      if (!resp.url().includes('/api/graphql')) return false;
      if (resp.request().method() !== 'POST') return false;
      const body = resp.request().postData() || '';
      return body.includes('browseEntities');
    }, { timeout: DATA_LOAD_TIMEOUT });

    await page.goto(TEXAS_PATH);
    const response = await responsePromise;

    const json = await response.json();
    const results = json.data?.browseEntities?.results || [];
    expect(results.length).toBeGreaterThan(1);

    const contributions = results.map((r: { metadata?: string }) => {
      try {
        const meta = JSON.parse(r.metadata || '{}');
        return meta.total_contributions || 0;
      } catch { return 0; }
    });

    for (let i = 1; i < contributions.length; i++) {
      expect(contributions[i - 1]).toBeGreaterThanOrEqual(contributions[i]);
    }
  });
});

test.describe('district profile - dynamic candidates', () => {
  const DISTRICT_PATH = '/districts/tx-10/';

  test('district profile loads candidates filtered to district', async ({ page }) => {
    const responsePromise = page.waitForResponse(resp => {
      if (!resp.url().includes('/api/graphql')) return false;
      if (resp.request().method() !== 'POST') return false;
      const body = resp.request().postData() || '';
      return body.includes('browseEntities');
    }, { timeout: DATA_LOAD_TIMEOUT });

    await page.goto(DISTRICT_PATH);
    const response = await responsePromise;

    const requestBody = JSON.parse(response.request().postData() || '{}');
    const filter = requestBody.variables?.filter || '';
    expect(filter).toContain('office:H');
    expect(filter).toContain('office_state:TX');
    expect(filter).toContain('office_district:10');

    const json = await response.json();
    const results = json.data?.browseEntities?.results || [];
    expect(results.length).toBeGreaterThan(0);
  });

  test('district profile candidates sorted by total contributions descending', async ({ page }) => {
    const responsePromise = page.waitForResponse(resp => {
      if (!resp.url().includes('/api/graphql')) return false;
      if (resp.request().method() !== 'POST') return false;
      const body = resp.request().postData() || '';
      return body.includes('browseEntities');
    }, { timeout: DATA_LOAD_TIMEOUT });

    await page.goto(DISTRICT_PATH);
    const response = await responsePromise;

    const json = await response.json();
    const results = json.data?.browseEntities?.results || [];
    expect(results.length).toBeGreaterThan(1);

    const contributions = results.map((r: { metadata?: string }) => {
      try {
        const meta = JSON.parse(r.metadata || '{}');
        return meta.total_contributions || 0;
      } catch { return 0; }
    });

    for (let i = 1; i < contributions.length; i++) {
      expect(contributions[i - 1]).toBeGreaterThanOrEqual(contributions[i]);
    }
  });
});

// Helper to intercept browseEntities GraphQL response and extract score values
async function getBrowseScores(page: import('@playwright/test').Page, url: string): Promise<number[]> {
  const responsePromise = page.waitForResponse(resp => {
    if (!resp.url().includes('/api/graphql')) return false;
    if (resp.request().method() !== 'POST') return false;
    const body = resp.request().postData() || '';
    return body.includes('browseEntities');
  }, { timeout: DATA_LOAD_TIMEOUT });
  await page.goto(url);
  const response = await responsePromise;
  const json = await response.json();
  const results = json.data?.browseEntities?.results || [];
  return results.map((r: { score?: number }) => r.score || 0);
}

test.describe('entity browse sort order - quantitative fields', () => {

  test('candidates sorted by total contributions descending', async ({ page }) => {
    const scores = await getBrowseScores(page, '/candidates/');
    expect(scores.length).toBeGreaterThan(1);
    for (let i = 1; i < scores.length; i++) {
      expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
    }
  });

  test('committees sorted by total receipts descending', async ({ page }) => {
    const scores = await getBrowseScores(page, '/committees/');
    expect(scores.length).toBeGreaterThan(1);
    for (let i = 1; i < scores.length; i++) {
      expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
    }
  });

  test('vendors sorted by total received descending', async ({ page }) => {
    const scores = await getBrowseScores(page, '/vendors/');
    expect(scores.length).toBeGreaterThan(1);
    for (let i = 1; i < scores.length; i++) {
      expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
    }
  });

  test('individuals sorted by total contributions descending', async ({ page }) => {
    const scores = await getBrowseScores(page, '/individuals/');
    expect(scores.length).toBeGreaterThan(1);
    for (let i = 1; i < scores.length; i++) {
      expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
    }
  });

  test('employers sorted by total employee contributions descending', async ({ page }) => {
    const scores = await getBrowseScores(page, '/employers/');
    expect(scores.length).toBeGreaterThan(1);
    for (let i = 1; i < scores.length; i++) {
      expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
    }
  });
});

test.describe('us.elect.info entity sections - additional index + detail', () => {

  test('employers section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/employers/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Employers/i);

    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('vendors section - index and random pages', async ({ page }) => {
    const indexResponse = await page.goto('/vendors/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Vendors/i);

    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('counties section - index and random detail pages', async ({ page }) => {
    await page.goto('/counties/');

    // Map view is default — verify Leaflet map renders with GeoJSON state shapes
    await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    const shapes = await page.locator('#map .leaflet-interactive').all();
    expect(shapes.length).toBeGreaterThan(0);

    // Switch to List view to test county results and detail navigation
    await page.locator('.toggle-btn[data-view="list"]').click();

    // Wait for results to load
    await page.waitForSelector('.county-result, .county-error', { timeout: DATA_LOAD_TIMEOUT });
    const results = await page.locator('.county-result').all();
    expect(results.length).toBeGreaterThan(0);

    // County results are <a> tags - pick random ones and visit detail pages
    const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('places section - index and random detail pages', async ({ page }) => {
    await page.goto('/places/');

    // Map view is default — verify Leaflet map renders with GeoJSON state shapes
    await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    const shapes = await page.locator('#map .leaflet-interactive').all();
    expect(shapes.length).toBeGreaterThan(0);

    // Switch to List view to test place results and detail navigation
    await page.locator('.toggle-btn[data-view="list"]').click();

    // Wait for results to load
    await page.waitForSelector('.place-result, .place-error', { timeout: DATA_LOAD_TIMEOUT });
    const results = await page.locator('.place-result').all();
    expect(results.length).toBeGreaterThan(0);

    // Pick random results and visit detail pages
    const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('postalcodes section - index and random detail pages', async ({ page }) => {
    await page.goto('/postalcodes/');

    // Map view is default — verify Leaflet map renders with GeoJSON state shapes
    await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    const shapes = await page.locator('#map .leaflet-interactive').all();
    expect(shapes.length).toBeGreaterThan(0);

    // Switch to List view to test postal code results and detail navigation
    await page.locator('.toggle-btn[data-view="list"]').click();

    // Wait for results to load
    await page.waitForSelector('.postal-result, .postal-error', { timeout: DATA_LOAD_TIMEOUT });
    const results = await page.locator('.postal-result').all();
    expect(results.length).toBeGreaterThan(0);

    // Pick random results and visit detail pages
    const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });

  test('precincts section - map view and state filter', async ({ page }) => {
    await page.goto('/precincts/');

    // Map view is default — verify Leaflet map renders with GeoJSON state shapes
    await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    const shapes = await page.locator('#map .leaflet-interactive').all();
    expect(shapes.length).toBeGreaterThan(0);

    // Switch to List view to test state filter and precinct results
    await page.locator('.toggle-btn[data-view="list"]').click();

    // Select a state first (precincts require state selection)
    const stateSelect = page.locator('#precinct-state-select');
    await expect(stateSelect).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
    await stateSelect.selectOption('TX');

    // Wait for precinct results to load
    await page.waitForSelector('.precinct-result, .precinct-error', { timeout: DATA_LOAD_TIMEOUT });
    const results = await page.locator('.precinct-result').all();
    expect(results.length).toBeGreaterThan(0);

    // Pick random results and visit detail pages
    const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
    const validHrefs = hrefs.filter(Boolean) as string[];

    if (validHrefs.length > 0) {
      const randomLinks = getRandomItems(validHrefs, 3);
      for (const href of randomLinks) {
        const entityResponse = await page.goto(toRelativePath(href));
        expect(entityResponse?.status()).toBe(200);
        await expect(page.locator('h1')).toBeVisible();
      }
    }
  });

  test('tracts section - state/county filter and random detail pages', async ({ page }) => {
    test.setTimeout(60000); // Tracts require sequential state→county→tract interactions
    await page.goto('/tracts/');

    // Wait for state dropdown to populate
    const stateSelect = page.locator('#tract-state-select');
    await page.waitForFunction(() => {
      const select = document.querySelector('#tract-state-select') as HTMLSelectElement;
      return select && select.options.length > 1;
    }, { timeout: DATA_LOAD_TIMEOUT });

    // Select Texas
    await stateSelect.selectOption('TX');

    // Wait for county dropdown to populate
    const countySelect = page.locator('#tract-county-select');
    await expect(countySelect).toBeEnabled({ timeout: DATA_LOAD_TIMEOUT });
    await page.waitForFunction(() => {
      const select = document.querySelector('#tract-county-select') as HTMLSelectElement;
      return select && select.options.length > 1;
    }, { timeout: DATA_LOAD_TIMEOUT });

    // Select first county
    const options = await countySelect.locator('option').all();
    if (options.length > 1) {
      const value = await options[1].getAttribute('value');
      if (value) await countySelect.selectOption(value);
    }

    // Wait for tract results to load
    await page.waitForSelector('.tract-result, .tract-error', { timeout: DATA_LOAD_TIMEOUT });
    const results = await page.locator('.tract-result').all();
    expect(results.length).toBeGreaterThan(0);

    // Pick random results and visit detail pages
    const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
    const validHrefs = hrefs.filter(Boolean) as string[];

    if (validHrefs.length > 0) {
      const randomLinks = getRandomItems(validHrefs, 3);
      for (const href of randomLinks) {
        const entityResponse = await page.goto(toRelativePath(href));
        expect(entityResponse?.status()).toBe(200);
        await expect(page.locator('h1')).toBeVisible();
      }
    }
  });

});

test.describe('us.elect.info entity sections - individuals (premium)', () => {
  test.use({
    httpCredentials: hasAuthCredentials ? {
      username: process.env.PLAYWRIGHT_AUTH_USER!,
      password: process.env.PLAYWRIGHT_AUTH_PASS || '',
    } : undefined
  });

  test('individuals section - index and random pages', async ({ page }) => {
    test.skip(!hasAuthCredentials, 'Requires PLAYWRIGHT_AUTH_USER/PLAYWRIGHT_AUTH_PASS');

    const indexResponse = await page.goto('/individuals/');
    expect(indexResponse?.status()).toBe(200);
    await expect(page.locator('h1')).toContainText(/Individual/i);

    await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });

    const links = await page.locator('.entity-search-result').all();
    expect(links.length).toBeGreaterThan(0);

    const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
    const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);

    for (const href of randomLinks) {
      const entityResponse = await page.goto(toRelativePath(href));
      expect(entityResponse?.status()).toBe(200);
      await expect(page.locator('h1')).toBeVisible();
    }
  });
});
