import { test, expect, type Locator, type Page } from "@playwright/test";
import fs from "fs/promises";
import path from "path";

import { AnalyzerPage } from "./pages/analyzer-page";

const headersPath = path.resolve(__dirname, "fixtures/sample-headers.txt");

const viewports = [
  { width: 320, height: 900, label: "320" },
  { width: 768, height: 900, label: "768" },
  { width: 1024, height: 900, label: "1024" },
  { width: 1440, height: 900, label: "1440" },
  { width: 2560, height: 1200, label: "2560" },
];

const assertNoHorizontalOverflow = async (page: Page, label: string) => {
  const metrics = await page.evaluate(() => {
    const docEl = document.documentElement;
    const body = document.body;
    return {
      docScrollWidth: docEl.scrollWidth,
      docClientWidth: docEl.clientWidth,
      bodyScrollWidth: body.scrollWidth,
      bodyClientWidth: body.clientWidth,
    };
  });

  expect(metrics.docScrollWidth, `document overflow at ${label}px`).toBeLessThanOrEqual(
    metrics.docClientWidth,
  );
  expect(metrics.bodyScrollWidth, `body overflow at ${label}px`).toBeLessThanOrEqual(
    metrics.bodyClientWidth,
  );
};

const assertActionable = async (
  locator: Locator,
  label: string,
  viewportWidth: number,
  options: { requireEnabled?: boolean } = {},
) => {
  const { requireEnabled = true } = options;
  await locator.scrollIntoViewIfNeeded();
  await expect(locator, `${label} should be visible`).toBeVisible();
  if (requireEnabled) {
    await expect(locator, `${label} should be enabled`).toBeEnabled();
  }
  await locator.click({ trial: true, force: true });

  const box = await locator.boundingBox();
  expect(box, `${label} missing layout box`).not.toBeNull();
  if (box) {
    expect(box.x, `${label} clipped on left`).toBeGreaterThanOrEqual(0);
    expect(box.x + box.width, `${label} clipped on right`).toBeLessThanOrEqual(
      viewportWidth + 1,
    );
  }
};

const assertActionableIfPresent = async (
  locator: Locator,
  label: string,
  viewportWidth: number,
  options: { requireEnabled?: boolean } = {},
) => {
  if ((await locator.count()) === 0) {
    return;
  }
  await assertActionable(locator, label, viewportWidth, options);
};

const assertReadableText = async (page: Page, label: string) => {
  const overflowIds = await page.evaluate(() => {
    const selectors = [
      '[data-testid="test-selector"]',
      '[data-testid="report-container"]',
      '[data-testid="report-export"]',
      '[data-testid="report-search-bar"]',
    ];

    const offenders: string[] = [];
    for (const selector of selectors) {
      const element = document.querySelector(selector) as HTMLElement | null;
      if (!element) {
        continue;
      }
      if (element.scrollWidth > element.clientWidth + 1) {
        offenders.push(selector);
      }
    }

    const cards = Array.from(
      document.querySelectorAll('[data-testid^="test-result-card-"]'),
    ).slice(0, 3);
    cards.forEach((card, index) => {
      const element = card as HTMLElement;
      if (element.scrollWidth > element.clientWidth + 1) {
        offenders.push(`test-result-card-${index}`);
      }
    });

    return offenders;
  });

  expect(overflowIds, `text overflow detected at ${label}px`).toEqual([]);
};

const ensureTestSelectorOpen = async (page: Page) => {
  const testSelector = page.getByTestId("test-selector");
  await testSelector.waitFor({ state: "visible" });
  await testSelector.evaluate((node) => {
    const details = node.querySelector("details");
    if (details && !details.open) {
      details.open = true;
    }
  });
};

const assertCardsStacked = async (page: Page, label: string, viewportWidth: number) => {
  const cards = page.locator('[data-testid^="test-result-card-"]');
  const count = await cards.count();
  if (count < 2) {
    return;
  }

  const firstBox = await cards.nth(0).boundingBox();
  const secondBox = await cards.nth(1).boundingBox();

  if (!firstBox || !secondBox) {
    throw new Error("Unable to read report card layout bounds.");
  }

  expect(
    secondBox.y,
    `report cards should stack vertically at ${label}px`,
  ).toBeGreaterThan(firstBox.y + firstBox.height - 4);

  const xDelta = Math.abs(secondBox.x - firstBox.x);
  expect(xDelta, `report cards should align at ${label}px`).toBeLessThanOrEqual(12);
  expect(firstBox.width, `report cards should use available width at ${label}px`).toBeGreaterThan(
    viewportWidth * 0.6,
  );
};

test("responsive layout remains usable across key breakpoints", async ({ page }) => {
  const headers = await fs.readFile(headersPath, "utf8");
  const analyzer = new AnalyzerPage(page);

  await analyzer.goto();
  await analyzer.pasteHeaders(headers);
  await analyzer.clickAnalyse();
  await analyzer.waitForResults();
  await ensureTestSelectorOpen(page);

  const firstCheckbox = page.locator('[data-testid^="test-checkbox-"]').first();
  await expect(firstCheckbox).toBeVisible({ timeout: 30000 });
  await expect(page.getByRole("button", { name: "Clear Cache" })).toBeEnabled();

  const reportContainer = page.getByTestId("report-container");
  await expect(reportContainer).toBeVisible();

  for (const viewport of viewports) {
    await page.setViewportSize({ width: viewport.width, height: viewport.height });
    await page.waitForTimeout(200);
    await ensureTestSelectorOpen(page);

    await assertNoHorizontalOverflow(page, viewport.label);
    await assertReadableText(page, viewport.label);

    await assertActionable(page.getByRole("textbox", { name: "Header Input" }), "Header input", viewport.width);
    await assertActionable(
      page.getByRole("button", { name: "Clear header input" }),
      "Clear header input",
      viewport.width,
      { requireEnabled: false },
    );
    await assertActionable(
      page.getByRole("button", { name: "Analyse Headers" }),
      "Analyse button",
      viewport.width,
      { requireEnabled: false },
    );
    await assertActionable(page.getByTestId("toggle-resolve"), "DNS toggle", viewport.width);
    await assertActionable(page.getByTestId("toggle-decode-all"), "Decode all toggle", viewport.width);
    await assertActionable(page.getByTestId("test-search-input"), "Test search input", viewport.width);
    await assertActionable(page.getByTestId("select-all-tests"), "Select all tests", viewport.width);
    await assertActionable(page.getByTestId("deselect-all-tests"), "Deselect all tests", viewport.width);
    await assertActionable(firstCheckbox, "First test checkbox", viewport.width);
    await assertActionableIfPresent(
      page.getByTestId("report-search-input"),
      "Report search input",
      viewport.width,
    );
    await assertActionableIfPresent(
      page.getByTestId("report-export-json"),
      "Export JSON",
      viewport.width,
    );
    await assertActionableIfPresent(
      page.getByTestId("report-export-html"),
      "Export HTML",
      viewport.width,
    );
    await assertActionable(
      page.getByRole("button", { name: "Clear Cache" }),
      "Clear cache button",
      viewport.width,
      { requireEnabled: false },
    );

    if (viewport.width <= 768) {
      await assertCardsStacked(page, viewport.label, viewport.width);
    }
  }
});
