// #267: e2e coverage for the sticky top bar / schedule heading / table
// header on the Executive Meetings page. We seed enough rows on a
// far-future date that the page is comfortably taller than the
// viewport, scroll the page, and assert that all three sticky
// elements line up flush with the top of the viewport (header at
// y=0, heading row directly under it, then the column header row).
// The column header must stick at every viewport width — mobile,
// tablet, and desktop.
import { test, expect } from "@playwright/test";
import pg from "pg";

const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
  throw new Error(
    "DATABASE_URL must be set to run the sticky-header UI test",
  );
}

const pool = new pg.Pool({ connectionString: DATABASE_URL });
const createdMeetingIds = [];

async function loginViaUi(page, username, password) {
  await page.goto("/login");
  await page.locator("#username").fill(username);
  await page.locator("#password").fill(password);
  await Promise.all([
    page.waitForURL((url) => !url.pathname.endsWith("/login"), {
      timeout: 15_000,
    }),
    page.locator('form button[type="submit"]').click(),
  ]);
}

async function setLangEn(page) {
  await page.addInitScript(() => {
    try {
      window.localStorage.setItem("tx-lang", "en");
    } catch {
      /* ignore */
    }
  });
}

const RANDOM_DATE_BASE_DAYS =
  365 + Math.floor(Math.random() * 1000) + ((Date.now() / 1000) | 0) % 500;
function uniqueFutureDate(offsetDays) {
  const d = new Date();
  d.setUTCDate(d.getUTCDate() + RANDOM_DATE_BASE_DAYS + offsetDays * 7);
  return d.toISOString().slice(0, 10);
}

async function insertMeeting({
  meetingDate,
  dailyNumber,
  titleEn,
  titleAr,
  startTime,
  endTime,
}) {
  const { rows } = await pool.query(
    `INSERT INTO executive_meetings
       (daily_number, title_ar, title_en, meeting_date, start_time, end_time, status)
     VALUES ($1, $2, $3, $4, $5, $6, 'scheduled')
     RETURNING id`,
    [dailyNumber, titleAr, titleEn, meetingDate, startTime, endTime],
  );
  const id = rows[0].id;
  createdMeetingIds.push(id);
  return id;
}

test.afterAll(async () => {
  if (createdMeetingIds.length > 0) {
    await pool.query(
      `DELETE FROM executive_meeting_attendees WHERE meeting_id = ANY($1::int[])`,
      [createdMeetingIds],
    );
    await pool.query(
      `DELETE FROM executive_meeting_audit_logs WHERE entity_type = 'meeting' AND entity_id = ANY($1::int[])`,
      [createdMeetingIds],
    );
    await pool.query(
      `DELETE FROM executive_meetings WHERE id = ANY($1::int[])`,
      [createdMeetingIds],
    );
  }
  await pool.end();
});

// The floating sticky thead renders before the (display:none) actual
// thead in the DOM, so .first() reliably targets the visible one.
function visibleColHeader(page, colId) {
  return page.getByTestId(`em-col-header-${colId}`).first();
}

async function assertStickyAtTop(page, { allowFloatingY = 4 } = {}) {
  const header = page.getByTestId("em-page-header");
  const heading = page.getByTestId("em-schedule-heading-bar");
  const colHeader = visibleColHeader(page, "meeting");

  await expect(header).toBeVisible();
  await expect(heading).toBeVisible();
  await expect(colHeader).toBeVisible();

  const headerBox = await header.boundingBox();
  const headingBox = await heading.boundingBox();
  const colBox = await colHeader.boundingBox();
  expect(headerBox).not.toBeNull();
  expect(headingBox).not.toBeNull();
  expect(colBox).not.toBeNull();

  // Page header is flush with the viewport top.
  expect(headerBox.y).toBeGreaterThanOrEqual(-1);
  expect(headerBox.y).toBeLessThanOrEqual(1);

  // Heading row sits directly below the page header.
  const expectedHeadingY = headerBox.y + headerBox.height;
  expect(headingBox.y).toBeGreaterThanOrEqual(expectedHeadingY - 2);
  expect(headingBox.y).toBeLessThan(expectedHeadingY + 4);

  // Column header sits directly below the heading row — the
  // critical assertion: it actually stuck instead of scrolling
  // away with the table.
  const expectedColY = headingBox.y + headingBox.height;
  expect(colBox.y).toBeGreaterThanOrEqual(expectedColY - 2);
  expect(colBox.y).toBeLessThan(expectedColY + allowFloatingY);
}

test("Schedule [desktop, LTR]: page header, heading row, and column header all stick to the top of the viewport on scroll", async ({
  page,
}) => {
  await setLangEn(page);

  const date = uniqueFutureDate(2);
  const seededIds = [];
  for (let i = 1; i <= 30; i++) {
    const hh = String(7 + Math.floor(i / 4)).padStart(2, "0");
    const mm = String((i % 4) * 15).padStart(2, "0");
    const id = await insertMeeting({
      meetingDate: date,
      dailyNumber: i,
      titleEn: `StickyTest Meeting ${i}`,
      titleAr: `اجتماع ثبات ${i}`,
      startTime: `${hh}:${mm}:00`,
      endTime: `${hh}:${mm}:00`,
    });
    seededIds.push(id);
  }

  await loginViaUi(page, "admin", "admin123");
  await page.setViewportSize({ width: 1280, height: 720 });
  await page.goto("/executive-meetings");

  const dateInput = page.locator('input[type="date"]').first();
  await dateInput.fill(date);

  await expect(page.getByTestId(`em-row-${seededIds[0]}`)).toBeVisible({
    timeout: 10_000,
  });

  await page.evaluate(() => window.scrollTo(0, 1500));
  await page.waitForTimeout(200);

  await assertStickyAtTop(page);
});

test("Schedule [desktop, RTL]: page header, heading row, and column header all stick after scroll in Arabic", async ({
  page,
}) => {
  await page.addInitScript(() => {
    try {
      window.localStorage.setItem("tx-lang", "ar");
    } catch {
      /* ignore */
    }
  });

  const date = uniqueFutureDate(3);
  const seededIds = [];
  for (let i = 1; i <= 30; i++) {
    const hh = String(7 + Math.floor(i / 4)).padStart(2, "0");
    const mm = String((i % 4) * 15).padStart(2, "0");
    const id = await insertMeeting({
      meetingDate: date,
      dailyNumber: i,
      titleEn: `StickyTest RTL ${i}`,
      titleAr: `اجتماع ثبات RTL ${i}`,
      startTime: `${hh}:${mm}:00`,
      endTime: `${hh}:${mm}:00`,
    });
    seededIds.push(id);
  }

  await loginViaUi(page, "admin", "admin123");
  await page.setViewportSize({ width: 1280, height: 720 });
  await page.goto("/executive-meetings");

  const dateInput = page.locator('input[type="date"]').first();
  await dateInput.fill(date);

  await expect(page.getByTestId(`em-row-${seededIds[0]}`)).toBeVisible({
    timeout: 10_000,
  });

  // Sanity-check the document is actually mirrored.
  const dir = await page.evaluate(() => document.documentElement.dir);
  expect(dir).toBe("rtl");

  await page.evaluate(() => window.scrollTo(0, 1500));
  await page.waitForTimeout(200);

  await assertStickyAtTop(page);
});

test("Schedule [tablet, ~900px]: column header still sticks at sub-xl widths", async ({
  page,
}) => {
  await setLangEn(page);

  const date = uniqueFutureDate(4);
  const seededIds = [];
  for (let i = 1; i <= 20; i++) {
    const hh = String(7 + Math.floor(i / 4)).padStart(2, "0");
    const mm = String((i % 4) * 15).padStart(2, "0");
    const id = await insertMeeting({
      meetingDate: date,
      dailyNumber: i,
      titleEn: `StickyTest Tablet ${i}`,
      titleAr: `اجتماع ضيق ${i}`,
      startTime: `${hh}:${mm}:00`,
      endTime: `${hh}:${mm}:00`,
    });
    seededIds.push(id);
  }

  await loginViaUi(page, "admin", "admin123");
  await page.setViewportSize({ width: 900, height: 700 });
  await page.goto("/executive-meetings");

  const dateInput = page.locator('input[type="date"]').first();
  await dateInput.fill(date);

  await expect(page.getByTestId(`em-row-${seededIds[0]}`)).toBeVisible({
    timeout: 10_000,
  });

  await page.evaluate(() => window.scrollTo(0, 1500));
  await page.waitForTimeout(200);

  await assertStickyAtTop(page);
});

test("Schedule [mobile, 414px]: column header still sticks on a narrow phone-sized viewport", async ({
  page,
}) => {
  await setLangEn(page);

  const date = uniqueFutureDate(5);
  const seededIds = [];
  for (let i = 1; i <= 20; i++) {
    const hh = String(7 + Math.floor(i / 4)).padStart(2, "0");
    const mm = String((i % 4) * 15).padStart(2, "0");
    const id = await insertMeeting({
      meetingDate: date,
      dailyNumber: i,
      titleEn: `StickyTest Mobile ${i}`,
      titleAr: `اجتماع جوال ${i}`,
      startTime: `${hh}:${mm}:00`,
      endTime: `${hh}:${mm}:00`,
    });
    seededIds.push(id);
  }

  await loginViaUi(page, "admin", "admin123");
  await page.setViewportSize({ width: 414, height: 800 });
  await page.goto("/executive-meetings");

  const dateInput = page.locator('input[type="date"]').first();
  await dateInput.fill(date);

  await expect(page.getByTestId(`em-row-${seededIds[0]}`)).toBeVisible({
    timeout: 10_000,
  });

  await page.evaluate(() => window.scrollTo(0, 1500));
  await page.waitForTimeout(200);

  await assertStickyAtTop(page);
});
