import { describe, expect, it, vi } from "vitest";
import { cspMiddleware, CspNonceContext } from "./csp.js";

function createMockRes() {
  const headers = new Map<string, string>();
  return {
    setHeader: vi.fn((name: string, value: string) => {
      headers.set(name, value);
    }),
    headers,
  };
}

describe("cspMiddleware", () => {
  it("sets Content-Security-Policy header with a nonce", () => {
    const middleware = cspMiddleware();
    const req = {} as Record<string, unknown>;
    const res = createMockRes();
    const next = vi.fn();

    middleware(req, res, next);

    expect(res.setHeader).toHaveBeenCalledWith(
      "Content-Security-Policy",
      expect.stringContaining("'nonce-"),
    );
    expect(next).toHaveBeenCalled();
  });

  it("generates unique nonce per call", () => {
    const middleware = cspMiddleware();
    const nonces: string[] = [];

    for (let i = 0; i < 5; i++) {
      const req = {} as Record<string, unknown>;
      const res = createMockRes();
      middleware(req, res, vi.fn());
      const csp = res.headers.get("Content-Security-Policy") ?? "";
      const match = csp.match(/nonce-([^']+)/);
      if (match?.[1]) nonces.push(match[1]);
    }

    const unique = new Set(nonces);
    expect(unique.size).toBe(5);
  });

  it("uses Content-Security-Policy-Report-Only when reportOnly is true", () => {
    const middleware = cspMiddleware({ reportOnly: true });
    const req = {} as Record<string, unknown>;
    const res = createMockRes();
    const next = vi.fn();

    middleware(req, res, next);

    expect(res.setHeader).toHaveBeenCalledWith(
      "Content-Security-Policy-Report-Only",
      expect.stringContaining("'nonce-"),
    );
  });

  it("attaches nonce to req.cspNonce", () => {
    const middleware = cspMiddleware();
    const req = {} as Record<string, unknown>;
    const res = createMockRes();

    middleware(req, res, vi.fn());

    expect(typeof req["cspNonce"]).toBe("string");
    expect((req["cspNonce"] as string).length).toBeGreaterThan(0);
  });
});

describe("CspNonceContext", () => {
  it("has a default value of empty string", () => {
    // Access the _currentValue (React internals) or check via default
    // We just verify the context exists and is created properly
    expect(CspNonceContext).toBeDefined();
    // The default value is "" — we test this by checking the context's _defaultValue
    // In React, createContext stores default as _currentValue
    expect((CspNonceContext as unknown as { _currentValue: string })._currentValue).toBe("");
  });
});
