/* @vitest-environment node */

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

vi.mock("@/lib/auth/session", () => ({
	getSession: vi.fn(),
	createSession: vi.fn(),
}));

vi.mock("@/lib/db", () => ({
	getDb: vi.fn(),
}));

vi.mock("@/models/user", () => ({
	default: {
		findById: vi.fn(),
	},
}));

vi.mock("bcryptjs", () => {
	const compare = vi.fn();
	const hash = vi.fn();
	return {
		default: { compare, hash },
		compare,
		hash,
	};
});

import { getSession, createSession } from "@/lib/auth/session";
import { getDb } from "@/lib/db";
import User from "@/models/user";
import { compare as bcryptCompare, hash as bcryptHash } from "bcryptjs";

import { POST, dynamic } from "./route.js";

function createRequestStub(body) {
	return {
		async json() {
			return body;
		},
	};
}

describe("POST /api/auth/change-password", () => {
	beforeEach(() => {
		vi.clearAllMocks();
		getDb.mockResolvedValue({});
	});

	it('exports dynamic="force-dynamic"', () => {
		expect(dynamic).toBe("force-dynamic");
	});

	it("returns 401 when unauthenticated", async () => {
		getSession.mockResolvedValue(null);

		const res = await POST(createRequestStub({}));
		expect(res.status).toBe(401);

		expect(await res.json()).toEqual({
			error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" },
		});
	});

	it("returns 400 when JSON parsing fails", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
		});

		const req = {
			json: vi.fn().mockRejectedValue(new Error("invalid json")),
		};

		const res = await POST(req);
		expect(res.status).toBe(400);

		expect(await res.json()).toEqual({
			error: {
				message: "Invalid request body",
				code: "VALIDATION_INVALID_JSON",
			},
		});
	});

	it("returns 400 when body is not an object", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
		});

		const res = await POST(createRequestStub("nope"));
		expect(res.status).toBe(400);

		expect(await res.json()).toEqual({
			error: {
				message: "Invalid request body",
				code: "VALIDATION_INVALID_BODY",
			},
		});
	});

	it("returns 400 when fields are missing", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
		});

		const res = await POST(createRequestStub({ currentPassword: "x" }));
		expect(res.status).toBe(400);

		expect(await res.json()).toEqual({
			error: {
				message: "Missing currentPassword or newPassword",
				code: "VALIDATION_MISSING_FIELD",
				details: { fields: ["newPassword"] },
			},
		});

		expect(User.findById).not.toHaveBeenCalled();
	});

	it("returns 401 when user is not found (treat as invalid session)", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
		});

		User.findById.mockReturnValue({
			exec: vi.fn().mockResolvedValue(null),
		});

		const res = await POST(
			createRequestStub({
				currentPassword: "OldPassword123",
				newPassword: "StrongPassword123",
			}),
		);

		expect(res.status).toBe(401);
		expect(await res.json()).toEqual({
			error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" },
		});
	});

	it("returns 401 when current password is wrong", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
		});

		const user = {
			_id: "507f1f77bcf86cd799439011",
			passwordHash: "hash",
			mustChangePassword: true,
			passwordResetToken: "tok",
			passwordResetExpiresAt: new Date(),
			save: vi.fn().mockResolvedValue(true),
		};

		User.findById.mockReturnValue({
			exec: vi.fn().mockResolvedValue(user),
		});

		bcryptCompare.mockResolvedValue(false);

		const res = await POST(
			createRequestStub({
				currentPassword: "wrong",
				newPassword: "StrongPassword123",
			}),
		);

		expect(res.status).toBe(401);
		expect(await res.json()).toEqual({
			error: {
				message: "Invalid credentials",
				code: "AUTH_INVALID_CREDENTIALS",
			},
		});

		expect(bcryptHash).not.toHaveBeenCalled();
		expect(user.save).not.toHaveBeenCalled();
		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 400 when new password is weak", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
		});

		const user = {
			_id: "507f1f77bcf86cd799439011",
			passwordHash: "hash",
			mustChangePassword: true,
			passwordResetToken: "tok",
			passwordResetExpiresAt: new Date(),
			save: vi.fn().mockResolvedValue(true),
		};

		User.findById.mockReturnValue({
			exec: vi.fn().mockResolvedValue(user),
		});

		bcryptCompare.mockResolvedValue(true);

		const res = await POST(
			createRequestStub({
				currentPassword: "OldPassword123",
				newPassword: "short",
			}),
		);

		expect(res.status).toBe(400);

		const body = await res.json();
		expect(body.error.code).toBe("VALIDATION_WEAK_PASSWORD");
		expect(body.error.details).toMatchObject({
			minLength: 8,
			requireLetter: true,
			requireNumber: true,
		});
		expect(Array.isArray(body.error.details.reasons)).toBe(true);

		expect(bcryptHash).not.toHaveBeenCalled();
		expect(user.save).not.toHaveBeenCalled();
		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 200 and updates passwordHash + clears flags on success", async () => {
		getSession.mockResolvedValue({
			userId: "u1",
			role: "branch",
			branchId: "NL01",
			email: "branch@example.com",
		});

		const user = {
			_id: "507f1f77bcf86cd799439011",
			passwordHash: "old-hash",
			role: "branch",
			branchId: "NL01",
			email: "branch@example.com",
			mustChangePassword: true,
			passwordResetToken: "tok",
			passwordResetExpiresAt: new Date("2030-01-01"),
			save: vi.fn().mockResolvedValue(true),
		};

		User.findById.mockReturnValue({
			exec: vi.fn().mockResolvedValue(user),
		});

		bcryptCompare.mockResolvedValue(true);
		bcryptHash.mockResolvedValue("new-hash");

		const res = await POST(
			createRequestStub({
				currentPassword: "OldPassword123",
				newPassword: "StrongPassword123",
			}),
		);

		expect(res.status).toBe(200);
		expect(await res.json()).toEqual({ ok: true });

		expect(bcryptHash).toHaveBeenCalledWith("StrongPassword123", 12);

		expect(user.passwordHash).toBe("new-hash");
		expect(user.mustChangePassword).toBe(false);
		expect(user.passwordResetToken).toBe(null);
		expect(user.passwordResetExpiresAt).toBe(null);

		expect(user.save).toHaveBeenCalledTimes(1);
		expect(createSession).toHaveBeenCalledWith({
			userId: "507f1f77bcf86cd799439011",
			role: "branch",
			branchId: "NL01",
			email: "branch@example.com",
			mustChangePassword: false,
		});
	});
});
