import { describe, it, expect } from "vitest";
import {
  buildSecurityHeaders,
  InvalidSecurityHeadersConfig,
} from "@/security/build-security-headers";

const ALL_SIX_HEADERS = [
  "Strict-Transport-Security",
  "X-Frame-Options",
  "X-Content-Type-Options",
  "Referrer-Policy",
  "Permissions-Policy",
  "Content-Security-Policy",
] as const;

describe("buildSecurityHeaders", () => {
  describe("prod mode — header set", () => {
    it("emits all six headers", () => {
      const headers = buildSecurityHeaders({ mode: "prod" });
      for (const name of ALL_SIX_HEADERS) {
        expect(headers).toHaveProperty(name);
      }
    });

    it("sets HSTS with preload", () => {
      const headers = buildSecurityHeaders({ mode: "prod" });
      expect(headers["Strict-Transport-Security"]).toContain(
        "max-age=31536000",
      );
      expect(headers["Strict-Transport-Security"]).toContain("preload");
    });

    it("sets X-Frame-Options to DENY", () => {
      expect(buildSecurityHeaders({ mode: "prod" })["X-Frame-Options"]).toBe(
        "DENY",
      );
    });

    it("sets X-Content-Type-Options to nosniff", () => {
      expect(
        buildSecurityHeaders({ mode: "prod" })["X-Content-Type-Options"],
      ).toBe("nosniff");
    });
  });

  describe("prod mode — CSP", () => {
    it("uses strict-dynamic in script-src", () => {
      const csp = buildSecurityHeaders({ mode: "prod" })[
        "Content-Security-Policy"
      ];
      expect(csp).toContain("script-src 'strict-dynamic'");
    });

    it("threads nonce into script-src when provided", () => {
      const nonce = "abc123==";
      const csp = buildSecurityHeaders({ mode: "prod", nonce })[
        "Content-Security-Policy"
      ];
      expect(csp).toContain(`'strict-dynamic' 'nonce-${nonce}'`);
    });

    it("omits nonce clause when not provided", () => {
      const csp = buildSecurityHeaders({ mode: "prod" })[
        "Content-Security-Policy"
      ];
      expect(csp).not.toContain("'nonce-");
    });

    it("applies allowedConnectOrigins to connect-src", () => {
      const csp = buildSecurityHeaders({
        mode: "prod",
        allowedConnectOrigins: ["https://api.example.com"],
      })["Content-Security-Policy"];
      expect(csp).toContain("connect-src 'self' https://api.example.com");
    });

    it("applies allowedImgOrigins to img-src", () => {
      const csp = buildSecurityHeaders({
        mode: "prod",
        allowedImgOrigins: ["https://images.example.com"],
      })["Content-Security-Policy"];
      expect(csp).toContain("img-src 'self' data: https://images.example.com");
    });

    it("applies allowedFontOrigins to font-src", () => {
      const csp = buildSecurityHeaders({
        mode: "prod",
        allowedFontOrigins: ["https://fonts.googleapis.com"],
      })["Content-Security-Policy"];
      expect(csp).toContain("font-src 'self' https://fonts.googleapis.com");
    });
  });

  describe("dev mode — header set", () => {
    it("emits all six headers", () => {
      const headers = buildSecurityHeaders({ mode: "dev" });
      for (const name of ALL_SIX_HEADERS) {
        expect(headers).toHaveProperty(name);
      }
    });
  });

  describe("dev mode — CSP", () => {
    it("uses unsafe-inline and unsafe-eval in script-src", () => {
      const csp = buildSecurityHeaders({ mode: "dev" })[
        "Content-Security-Policy"
      ];
      expect(csp).toContain("'unsafe-inline'");
      expect(csp).toContain("'unsafe-eval'");
    });

    it("includes ws: localhost:* 127.0.0.1:* in connect-src", () => {
      const csp = buildSecurityHeaders({ mode: "dev" })[
        "Content-Security-Policy"
      ];
      expect(csp).toContain("ws:");
      expect(csp).toContain("localhost:*");
      expect(csp).toContain("127.0.0.1:*");
    });

    it("applies allowedConnectOrigins to connect-src in dev mode", () => {
      const csp = buildSecurityHeaders({
        mode: "dev",
        allowedConnectOrigins: ["https://staging.example.com"],
      })["Content-Security-Policy"];
      expect(csp).toContain("https://staging.example.com");
    });

    it("applies allowedImgOrigins to img-src in dev mode", () => {
      const csp = buildSecurityHeaders({
        mode: "dev",
        allowedImgOrigins: ["https://images.example.com"],
      })["Content-Security-Policy"];
      expect(csp).toContain("img-src 'self' data: https://images.example.com");
    });

    it("applies allowedFontOrigins to font-src in dev mode", () => {
      const csp = buildSecurityHeaders({
        mode: "dev",
        allowedFontOrigins: ["https://fonts.googleapis.com"],
      })["Content-Security-Policy"];
      expect(csp).toContain("font-src 'self' https://fonts.googleapis.com");
    });
  });

  describe("URL validation", () => {
    it("throws InvalidSecurityHeadersConfig for malformed allowedConnectOrigins", () => {
      expect(() =>
        buildSecurityHeaders({
          mode: "prod",
          allowedConnectOrigins: ["not-a-url"],
        }),
      ).toThrow(InvalidSecurityHeadersConfig);
    });

    it("throws InvalidSecurityHeadersConfig for malformed allowedImgOrigins", () => {
      expect(() =>
        buildSecurityHeaders({
          mode: "prod",
          allowedImgOrigins: ["not-a-url"],
        }),
      ).toThrow(InvalidSecurityHeadersConfig);
    });

    it("throws InvalidSecurityHeadersConfig for malformed allowedFontOrigins", () => {
      expect(() =>
        buildSecurityHeaders({
          mode: "prod",
          allowedFontOrigins: ["not-a-url"],
        }),
      ).toThrow(InvalidSecurityHeadersConfig);
    });

    it("error message names the invalid origin", () => {
      expect(() =>
        buildSecurityHeaders({
          mode: "prod",
          allowedConnectOrigins: ["://bad"],
        }),
      ).toThrow(/allowedConnectOrigins/);
    });

    it("accepts valid https origins without throwing", () => {
      expect(() =>
        buildSecurityHeaders({
          mode: "prod",
          allowedConnectOrigins: ["https://api.example.com"],
          allowedImgOrigins: ["https://cdn.example.com"],
          allowedFontOrigins: ["https://fonts.googleapis.com"],
        }),
      ).not.toThrow();
    });
  });
});
