import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { Hono } from "hono";
import { securityHeaders } from "./security-headers.js";
import { _resetConfigForTesting } from "./config.js";

describe("securityHeaders middleware", () => {
  const EXPECTED_HEADERS = {
    "x-content-type-options": "nosniff",
    "x-frame-options": "DENY",
    "referrer-policy": "strict-origin-when-cross-origin",
    "x-xss-protection": "0",
    "content-security-policy":
      "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; " +
      "img-src 'self' data: blob:; connect-src 'self' ws: wss:; font-src 'self'; " +
      "object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'",
    "permissions-policy":
      "camera=(), microphone=(), geolocation=(), payment=()",
  } as const;

  function createTestApp(): Hono {
    const app = new Hono();
    app.use("*", securityHeaders());
    app.get("/health", (c) => c.json({ ok: true }));
    app.post("/data", (c) => c.json({ saved: true }));
    return app;
  }

  it("should set all security headers on GET responses", async () => {
    const app = createTestApp();
    const res = await app.request("/health");

    expect(res.status).toBe(200);
    for (const [header, value] of Object.entries(EXPECTED_HEADERS)) {
      expect(res.headers.get(header)).toBe(value);
    }
  });

  it("should set all security headers on POST responses", async () => {
    const app = createTestApp();
    const res = await app.request("/data", { method: "POST" });

    expect(res.status).toBe(200);
    for (const [header, value] of Object.entries(EXPECTED_HEADERS)) {
      expect(res.headers.get(header)).toBe(value);
    }
  });

  it("should set security headers on 404 responses", async () => {
    const app = createTestApp();
    const res = await app.request("/nonexistent");

    expect(res.status).toBe(404);
    for (const [header, value] of Object.entries(EXPECTED_HEADERS)) {
      expect(res.headers.get(header)).toBe(value);
    }
  });

  it("should not overwrite Content-Type set by route handler", async () => {
    const app = createTestApp();
    const res = await app.request("/health");

    expect(res.headers.get("content-type")).toContain("application/json");
    expect(res.headers.get("x-content-type-options")).toBe("nosniff");
  });

  it("should set X-XSS-Protection to 0 (disabled)", async () => {
    const app = createTestApp();
    const res = await app.request("/health");

    // Modern best practice: disable the legacy XSS filter
    expect(res.headers.get("x-xss-protection")).toBe("0");
  });

  describe("HSTS (Strict-Transport-Security)", () => {
    let prevTrustProxy: string | undefined;

    beforeEach(() => {
      prevTrustProxy = process.env.ZENCODE_TRUST_PROXY;
    });

    afterEach(() => {
      // Restore original env value
      if (prevTrustProxy === undefined) {
        delete process.env.ZENCODE_TRUST_PROXY;
      } else {
        process.env.ZENCODE_TRUST_PROXY = prevTrustProxy;
      }
      _resetConfigForTesting();
    });

    it("should not set HSTS header when ZENCODE_TRUST_PROXY is unset", async () => {
      delete process.env.ZENCODE_TRUST_PROXY;
      _resetConfigForTesting();

      const app = createTestApp();
      const res = await app.request("/health");

      expect(res.headers.get("strict-transport-security")).toBeNull();
    });

    it("should set HSTS header when ZENCODE_TRUST_PROXY is enabled", async () => {
      process.env.ZENCODE_TRUST_PROXY = "1";
      _resetConfigForTesting();

      const app = createTestApp();
      const res = await app.request("/health");

      expect(res.headers.get("strict-transport-security")).toBe(
        "max-age=31536000; includeSubDomains",
      );
    });

    it("should not set HSTS header when ZENCODE_TRUST_PROXY is '0'", async () => {
      process.env.ZENCODE_TRUST_PROXY = "0";
      _resetConfigForTesting();

      const app = createTestApp();
      const res = await app.request("/health");

      expect(res.headers.get("strict-transport-security")).toBeNull();
    });
  });
});
