/* @vitest-environment node */

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

// Mock next/headers to provide a simple in-memory cookie store
vi.mock("next/headers", () => {
	let store = new Map();

	return {
		cookies() {
			return {
				get(name) {
					const entry = store.get(name);
					if (!entry) return undefined;
					return { name, value: entry.value };
				},
				set(name, value, options) {
					store.set(name, { value, options });
				},
			};
		},
		__cookieStore: {
			clear() {
				store = new Map();
			},
			dump() {
				return store;
			},
		},
	};
});

// Import after the mock so the module under test uses the mocked cookies()
import {
	createSession,
	getSession,
	destroySession,
	SESSION_COOKIE_NAME,
	SESSION_MAX_AGE_SECONDS,
} from "./session";
import { __cookieStore } from "next/headers";

describe("auth session utilities", () => {
	beforeEach(() => {
		__cookieStore.clear();
		process.env.SESSION_SECRET = "x".repeat(64);
		process.env.NODE_ENV = "test";
		delete process.env.SESSION_COOKIE_SECURE;
	});

	it("creates a session cookie with a signed JWT", async () => {
		const jwt = await createSession({
			userId: "user123",
			role: "branch",
			branchId: "NL01",
		});

		expect(typeof jwt).toBe("string");
		expect(jwt.length).toBeGreaterThan(10);

		const store = __cookieStore.dump();
		const cookie = store.get(SESSION_COOKIE_NAME);

		expect(cookie).toBeDefined();
		expect(cookie.value).toBe(jwt);

		expect(cookie.options).toMatchObject({
			httpOnly: true,
			secure: false, // NODE_ENV = "test"
			sameSite: "lax",
			path: "/",
			maxAge: SESSION_MAX_AGE_SECONDS,
		});
	});

	it("reads a valid session from cookie (email defaults to null)", async () => {
		await createSession({
			userId: "user456",
			role: "admin",
			branchId: null,
		});

		const session = await getSession();

		expect(session).toEqual({
			userId: "user456",
			role: "admin",
			branchId: null,
			email: null,
			mustChangePassword: false,
		});
	});

	it("includes email in the session when provided (normalized to lowercase)", async () => {
		await createSession({
			userId: "user999",
			role: "branch",
			branchId: "NL01",
			email: "User@Example.COM",
		});

		const session = await getSession();

		expect(session).toEqual({
			userId: "user999",
			role: "branch",
			branchId: "NL01",
			email: "user@example.com",
			mustChangePassword: false,
		});
	});

	it("includes mustChangePassword=true only when explicitly set to true", async () => {
		await createSession({
			userId: "user-must",
			role: "branch",
			branchId: "NL01",
			mustChangePassword: true,
		});

		const session = await getSession();

		expect(session).toEqual({
			userId: "user-must",
			role: "branch",
			branchId: "NL01",
			email: null,
			mustChangePassword: true,
		});
	});

	it("defaults mustChangePassword=false for legacy payloads without the field", async () => {
		const jwt = await new SignJWT({
			userId: "legacy-user",
			role: "admin",
			branchId: null,
			email: "legacy@example.com",
		})
			.setProtectedHeader({ alg: "HS256", typ: "JWT" })
			.setIssuedAt()
			.setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`)
			.sign(new TextEncoder().encode(process.env.SESSION_SECRET));

		const store = __cookieStore.dump();
		store.set(SESSION_COOKIE_NAME, {
			value: jwt,
			options: {
				httpOnly: true,
				secure: false,
				sameSite: "lax",
				path: "/",
				maxAge: SESSION_MAX_AGE_SECONDS,
			},
		});

		const session = await getSession();

		expect(session).toEqual({
			userId: "legacy-user",
			role: "admin",
			branchId: null,
			email: "legacy@example.com",
			mustChangePassword: false,
		});
	});

	it("returns null when no session cookie is present", async () => {
		const session = await getSession();
		expect(session).toBeNull();
	});

	it("returns null and clears cookie when token is invalid", async () => {
		// Manually set an invalid JWT value
		const store = __cookieStore.dump();
		store.set(SESSION_COOKIE_NAME, {
			value: "not-a-valid-jwt",
			options: {
				httpOnly: true,
				secure: false,
				sameSite: "lax",
				path: "/",
				maxAge: SESSION_MAX_AGE_SECONDS,
			},
		});

		const session = await getSession();
		expect(session).toBeNull();

		const updatedStore = __cookieStore.dump();
		const cookie = updatedStore.get(SESSION_COOKIE_NAME);

		expect(cookie).toBeDefined();
		expect(cookie.value).toBe("");
		expect(cookie.options.maxAge).toBe(0);
	});

	it("destroySession clears the session cookie when it exists", async () => {
		await createSession({
			userId: "user789",
			role: "branch",
			branchId: "NL02",
		});

		await destroySession();

		const store = __cookieStore.dump();
		const cookie = store.get(SESSION_COOKIE_NAME);

		expect(cookie).toBeDefined();
		expect(cookie.value).toBe("");
		expect(cookie.options.maxAge).toBe(0);
	});

	it("destroySession sets an empty cookie even if none existed before", async () => {
		await destroySession();

		const store = __cookieStore.dump();
		const cookie = store.get(SESSION_COOKIE_NAME);

		expect(cookie).toBeDefined();
		expect(cookie.value).toBe("");
		expect(cookie.options.maxAge).toBe(0);
	});

	it('respects SESSION_COOKIE_SECURE override when set to "true"', async () => {
		process.env.SESSION_COOKIE_SECURE = "true";

		await createSession({
			userId: "user-secure",
			role: "admin",
			branchId: null,
		});

		const store = __cookieStore.dump();
		const cookie = store.get(SESSION_COOKIE_NAME);

		expect(cookie.options.secure).toBe(true);
	});

	it('respects SESSION_COOKIE_SECURE override when set to "false"', async () => {
		process.env.SESSION_COOKIE_SECURE = "false";

		await createSession({
			userId: "user-insecure",
			role: "admin",
			branchId: null,
		});

		const store = __cookieStore.dump();
		const cookie = store.get(SESSION_COOKIE_NAME);

		expect(cookie.options.secure).toBe(false);
	});
});
