import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("next/server", () => ({
  NextResponse: {
    next: vi.fn(),
  },
}));

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { withSecurityHeaders } from "@/security/next/middleware";

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

function makeRequest(): NextRequest {
  return { headers: new Headers() } as unknown as NextRequest;
}

function makeResponseMock() {
  const store = new Map<string, string>();
  return {
    _store: store,
    headers: {
      set: vi.fn((k: string, v: string) => store.set(k, v)),
      get: vi.fn((k: string) => store.get(k) ?? null),
    },
  };
}

describe("withSecurityHeaders", () => {
  let mock: ReturnType<typeof makeResponseMock>;

  beforeEach(() => {
    mock = makeResponseMock();
    vi.mocked(NextResponse.next).mockReturnValue(
      mock as unknown as ReturnType<typeof NextResponse.next>,
    );
  });

  it("sets all six security headers on the response", () => {
    withSecurityHeaders(makeRequest());

    for (const header of ALL_SIX_HEADERS) {
      expect(mock._store.has(header)).toBe(true);
    }
  });

  it("sets x-nonce on the response", () => {
    withSecurityHeaders(makeRequest());

    const nonce = mock._store.get("x-nonce");
    expect(nonce).toBeDefined();
    expect(typeof nonce).toBe("string");
    expect((nonce as string).length).toBeGreaterThan(0);
  });

  it("nonce in x-nonce matches nonce threaded into CSP in production mode", () => {
    vi.stubEnv("NODE_ENV", "production");

    withSecurityHeaders(makeRequest());

    const nonce = mock._store.get("x-nonce");
    const csp = mock._store.get("Content-Security-Policy");
    expect(csp).toContain(`'nonce-${nonce}'`);
  });

  it("uses dev-mode CSP when NODE_ENV is not production", () => {
    vi.stubEnv("NODE_ENV", "test");

    withSecurityHeaders(makeRequest());

    expect(mock._store.get("Content-Security-Policy")).toContain(
      "'unsafe-inline'",
    );
  });

  it("forwards nonce via x-nonce in the request headers passed to NextResponse.next", () => {
    withSecurityHeaders(makeRequest());

    const call = vi.mocked(NextResponse.next).mock.calls[0] as [
      { request?: { headers?: Headers } } | undefined,
    ];
    expect(call[0]?.request?.headers?.get("x-nonce")).toBeTruthy();
  });

  it("returns the NextResponse from NextResponse.next", () => {
    const result = withSecurityHeaders(makeRequest());

    expect(result).toBe(mock);
  });
});
