/**
 * Security Headers Suite — Task A (HSTS / HTTPS-only)
 *
 * Verifies next.config.ts: the Strict-Transport-Security header is configured
 * with the full "max-age=63072000; includeSubDomains; preload" directive, is
 * emitted only when the request actually arrived over HTTPS
 * (x-forwarded-proto: https), and the remaining security headers stay
 * unconditional on the catch-all rule. Pure config logic — no database or
 * running server required.
 */

import { describe, test, expect } from "./runner";
import configModule from "../../next.config";

// Under tsx (ESM) the CJS-style default export can arrive wrapped as
// `{ default: nextConfig }`; unwrap it so `config.headers()` is callable in
// either interop mode.
const config = ((configModule as { default?: typeof configModule }).default ??
  configModule) as typeof configModule;

interface HeaderItem {
  key: string;
  value: string;
}

interface HeaderCondition {
  type: string;
  key: string;
  value?: string;
}

interface HeaderRule {
  source: string;
  has?: HeaderCondition[];
  headers: HeaderItem[];
}

async function catchAllRules(): Promise<HeaderRule[]> {
  // `headers` is optional on the NextConfig type; next.config.ts always defines it.
  const rules = (await config.headers!()) as HeaderRule[];
  return rules.filter((rule) => rule.source === "/(.*)");
}

function findHstsRule(rules: HeaderRule[]): HeaderRule | undefined {
  return rules.find((rule) =>
    rule.headers.some(
      (header) => header.key.toLowerCase() === "strict-transport-security"
    )
  );
}

describe("Security headers — HSTS (HTTPS-only)", () => {
  test("the Strict-Transport-Security header is configured on the catch-all rule", async () => {
    const rules = await catchAllRules();
    expect(findHstsRule(rules)).toBeDefined();
  });

  test("HSTS carries max-age=63072000, includeSubDomains and preload", async () => {
    const rule = findHstsRule(await catchAllRules());
    expect(rule).toBeDefined();
    const hsts = rule!.headers.find(
      (header) => header.key.toLowerCase() === "strict-transport-security"
    );
    expect(hsts).toBeDefined();
    expect(hsts!.value).toContain("max-age=63072000");
    expect(hsts!.value).toContain("includeSubDomains");
    expect(hsts!.value).toContain("preload");
  });

  test("HSTS is emitted only when the request arrived over HTTPS (x-forwarded-proto)", async () => {
    const rule = findHstsRule(await catchAllRules());
    expect(rule).toBeDefined();
    expect(rule!.has).toBeDefined();
    const conditioned = rule!.has!.some(
      (condition) =>
        condition.type === "header" &&
        condition.key.toLowerCase() === "x-forwarded-proto" &&
        condition.value === "https"
    );
    expect(conditioned).toBe(true);
  });

  test("the remaining security headers stay unconditional on the catch-all rule", async () => {
    const rules = await catchAllRules();
    const unconditional = rules.filter((rule) => !rule.has);
    expect(unconditional.length).toBeGreaterThan(0);

    const allHeaders = unconditional.flatMap((rule) => rule.headers);
    const keys = new Set(allHeaders.map((header) => header.key.toLowerCase()));

    expect(keys.has("x-frame-options")).toBe(true);
    expect(keys.has("x-content-type-options")).toBe(true);
    expect(keys.has("referrer-policy")).toBe(true);
    expect(keys.has("permissions-policy")).toBe(true);
    expect(keys.has("content-security-policy")).toBe(true);

    const xfo = allHeaders.find(
      (header) => header.key.toLowerCase() === "x-frame-options"
    );
    const xcto = allHeaders.find(
      (header) => header.key.toLowerCase() === "x-content-type-options"
    );
    expect(xfo).toBeDefined();
    expect(xcto).toBeDefined();
    expect(xfo!.value).toBe("DENY");
    expect(xcto!.value).toBe("nosniff");
    expect(
      allHeaders.some(
        (header) =>
          header.key.toLowerCase() === "referrer-policy" &&
          header.value.includes("strict-origin-when-cross-origin")
      )
    ).toBe(true);
    expect(
      allHeaders.some(
        (header) =>
          header.key.toLowerCase() === "content-security-policy" &&
          header.value.length > 0
      )
    ).toBe(true);
  });
});
