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

const responseMock = vi.hoisted(() => {
  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),
      },
    };
  }
  return { makeResponseMock };
});

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

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { middleware } from "../../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;
}

describe("web-next middleware", () => {
  let mock: ReturnType<typeof responseMock.makeResponseMock>;

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

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

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

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

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

  it("CSP contains nonce in production mode", () => {
    vi.stubEnv("NODE_ENV", "production");

    middleware(makeRequest());

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

  it("CSP is permissive (unsafe-inline) in development mode", () => {
    vi.stubEnv("NODE_ENV", "development");

    middleware(makeRequest());

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

  it("x-nonce is forwarded in request headers passed to NextResponse.next", () => {
    middleware(makeRequest());

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