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

import { LOCALES } from "../lib/locales";
import { href, indexableTargets } from "../lib/routes";

/**
 * Blueprint 13 and 17 rule 13: the legacy map is tested, and the old image and
 * plugin paths return 410 rather than being quietly forwarded — the four
 * screenshots that exposed personal data must not resolve to anything.
 *
 * Next.js answers `permanent: true` with 308 rather than 301. Both are
 * permanent and search engines treat them the same; 308 additionally
 * preserves the request method.
 */

const PERMANENT = [307, 308, 301];

test.describe("legacy URLs", () => {
  const cases: [string, string][] = [
    ["/index.php?Itemid=531", "/sq/nexys/financa"],
    ["/index.php?Itemid=532", "/sq/nexys/materiale"],
    ["/index.php?Itemid=533", "/sq/nexys/bar-restorant"],
    ["/index.php?Itemid=534", "/sq/nexys/paga"],
    ["/index.php?option=com_spsimpleportfolio&view=item&id=7:nexys-finance", "/sq/nexys/financa"],
    ["/index.php?option=com_spsimpleportfolio&view=item&id=8:nexysmat", "/sq/nexys/materiale"],
    [
      "/index.php?option=com_spsimpleportfolio&view=item&id=10:barrestaurant",
      "/sq/nexys/bar-restorant",
    ],
    ["/index.php?option=com_spsimpleportfolio&view=item&id=11:nexysrroga", "/sq/nexys/paga"],
    ["/kontakt.html", "/sq/kontakt"],
    ["/index.php", "/sq"],
  ];

  for (const [from, to] of cases) {
    test(`${from} lands on ${to}`, async ({ request }) => {
      const response = await request.get(from, { maxRedirects: 0 });
      expect(PERMANENT).toContain(response.status());
      expect(new URL(response.headers()["location"], "http://x").pathname).toBe(to);
    });
  }
});

test.describe("module brand-name aliases", () => {
  const aliases: [string, string][] = [
    ["/sq/nexys/nexys-mat", "/sq/nexys/materiale"],
    ["/sq/nexys/nexys-finance", "/sq/nexys/financa"],
    ["/sq/nexys/nexys-budget", "/sq/nexys/paga"],
    ["/sq/nexys/nexys-caffe", "/sq/nexys/bar-restorant"],
    ["/mk/nexys/nexys-mat", "/mk/nexys/materijalno"],
    ["/mk/nexys/nexys-caffe", "/mk/nexys/bar-restoran"],
  ];

  for (const [from, to] of aliases) {
    test(`${from} lands on ${to}`, async ({ request }) => {
      const response = await request.get(from, { maxRedirects: 0 });
      expect(PERMANENT).toContain(response.status());
      expect(new URL(response.headers()["location"], "http://x").pathname).toBe(to);
    });
  }
});

test.describe("the old asset trees are gone, not moved", () => {
  const gone = [
    "/images/sulsys/Finansisko.jpg",
    "/images/sulsys/Arka.jpg",
    "/images/sulsys/software/Rest_Fillimi.jpg",
    "/images/sulsys/software/PLataBuxhet.jpg",
    "/cache/mod_news_show_sp2/nssp2_thumbs/119/eko.jpg",
    "/media/system/js/core.js",
    "/templates/shaper_helix3/images/favicon.ico",
    "/administrator/index.php",
    "/plugins/system/x.php",
  ];

  for (const path of gone) {
    test(`${path} returns 410`, async ({ request }) => {
      const response = await request.get(path, { maxRedirects: 0 });
      expect(response.status()).toBe(410);
    });
  }
});

test.describe("locale entry", () => {
  test("/ redirects to a locale", async ({ request }) => {
    const response = await request.get("/", { maxRedirects: 0 });
    expect(PERMANENT).toContain(response.status());
    expect(response.headers()["location"]).toMatch(/\/(sq|mk)$/);
  });

  test("an unknown top-level path goes to the Albanian home page", async ({ request }) => {
    const response = await request.get("/some-old-joomla-page", { maxRedirects: 0 });
    expect(PERMANENT).toContain(response.status());
    expect(new URL(response.headers()["location"], "http://x").pathname).toBe("/sq");
  });

  test("an unknown path inside a locale reaches the localised 404", async ({ page }) => {
    const response = await page.goto("/mk/ne-postoi");
    expect(response?.status()).toBe(404);
    await expect(page.locator("html")).toHaveAttribute("lang", "mk");
  });
});

test.describe("security headers", () => {
  test("every response carries the policy set", async ({ request }) => {
    const response = await request.get("/sq");
    const headers = response.headers();

    expect(headers["content-security-policy"]).toContain("default-src 'none'");
    expect(headers["content-security-policy"]).toContain(
      "frame-src https://www.youtube-nocookie.com",
    );
    expect(headers["strict-transport-security"]).toContain("max-age=63072000");
    expect(headers["x-content-type-options"]).toBe("nosniff");
    expect(headers["x-frame-options"]).toBe("DENY");
    expect(headers["referrer-policy"]).toBe("strict-origin-when-cross-origin");
    expect(headers["permissions-policy"]).toContain("geolocation=()");
    expect(headers["x-powered-by"]).toBeUndefined();
  });

  test("the development relaxations never reach a built site", async ({ request }) => {
    // next.config.ts adds 'unsafe-eval' and ws: so that React's dev build and
    // Turbopack's hot reload work locally. A built site must have neither.
    const csp = (await request.get("/sq")).headers()["content-security-policy"];

    expect(csp).not.toContain("unsafe-eval");
    expect(csp).not.toContain("ws:");
    expect(csp).toContain("upgrade-insecure-requests");
  });
});

test.describe("sitemap and robots", () => {
  test("the sitemap lists every page in every locale, with alternates", async ({ request }) => {
    const body = await (await request.get("/sitemap.xml")).text();

    // Parity rather than a spot check: a language whose pages are missing
    // from the sitemap is a language search engines are slower to find, and
    // nothing else in the build would say so.
    for (const locale of LOCALES) {
      for (const target of indexableTargets()) {
        const path = href(target, locale);
        expect(body, path + " is missing from the sitemap").toContain(path + "</loc>");
      }
    }

    const urls = body.match(/<url>/g) ?? [];
    expect(urls).toHaveLength(LOCALES.length * indexableTargets().length);

    // Each entry names all four languages and the default.
    const alternates = body.match(/hreflang="x-default"/g) ?? [];
    expect(alternates).toHaveLength(urls.length);

    expect(body).not.toContain("/404");
  });

  test("a non-production build is not indexable", async ({ request }) => {
    const body = await (await request.get("/robots.txt")).text();
    // The suite runs outside Vercel production, so previews must be closed.
    expect(body).toContain("Disallow: /");
  });
});
