/* @vitest-environment node */

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

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

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

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

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

// 2) Imports AFTER the mocks
import { getDb } from "@/lib/db";
import User from "@/models/user";
import { createSession } from "@/lib/auth/session";
import { compare as bcryptCompare } from "bcryptjs";
import { POST, dynamic } from "./route.js";

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

describe("POST /api/auth/login", () => {
	beforeEach(() => {
		vi.clearAllMocks();
		getDb.mockResolvedValue({}); // we only need it to "connect"
	});

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

	it("logs in successfully with correct credentials", async () => {
		const user = {
			_id: "507f1f77bcf86cd799439011",
			username: "branchuser",
			email: "nl01@example.com",
			passwordHash: "hashed-password",
			role: "branch",
			branchId: "NL01",
			mustChangePassword: true,
		};

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

		bcryptCompare.mockResolvedValue(true);

		const request = createRequestStub({
			username: "BranchUser", // mixed case, should be normalized
			password: "secret-password",
		});

		const response = await POST(request);
		const json = await response.json();

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

		expect(getDb).toHaveBeenCalledTimes(1);
		expect(User.findOne).toHaveBeenCalledWith({ username: "branchuser" });

		expect(bcryptCompare).toHaveBeenCalledWith(
			"secret-password",
			"hashed-password",
		);

		expect(createSession).toHaveBeenCalledWith({
			userId: "507f1f77bcf86cd799439011",
			role: "branch",
			branchId: "NL01",
			email: "nl01@example.com",
			mustChangePassword: true,
		});
	});

	it("returns 400 when JSON parsing fails", async () => {
		// Simulate request.json() throwing (invalid JSON body).
		const request = {
			json: vi.fn().mockRejectedValue(new Error("invalid json")),
		};

		const response = await POST(request);
		const body = await response.json();

		expect(response.status).toBe(400);
		expect(body).toEqual({
			error: {
				message: "Invalid request body",
				code: "VALIDATION_INVALID_JSON",
			},
		});

		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 401 when user does not exist", async () => {
		User.findOne.mockReturnValue({
			exec: vi.fn().mockResolvedValue(null),
		});

		const request = createRequestStub({
			username: "unknownuser",
			password: "some-password",
		});

		const response = await POST(request);
		const body = await response.json();

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

		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 401 when passwordHash is missing (defensive)", async () => {
		User.findOne.mockReturnValue({
			exec: vi.fn().mockResolvedValue({
				_id: "507f1f77bcf86cd799439099",
				username: "branchuser",
				email: "nl01@example.com",
				// passwordHash missing on purpose
				role: "branch",
				branchId: "NL01",
			}),
		});

		const request = createRequestStub({
			username: "branchuser",
			password: "secret-password",
		});

		const response = await POST(request);
		const body = await response.json();

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

		expect(bcryptCompare).not.toHaveBeenCalled();
		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 401 when password is incorrect", async () => {
		const user = {
			_id: "507f1f77bcf86cd799439012",
			username: "branchuser",
			email: "nl02@example.com",
			passwordHash: "hashed-password",
			role: "branch",
			branchId: "NL02",
		};

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

		bcryptCompare.mockResolvedValue(false);

		const request = createRequestStub({
			username: "branchuser",
			password: "wrong-password",
		});

		const response = await POST(request);
		const body = await response.json();

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

		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 400 when username or password is missing", async () => {
		const request = createRequestStub({
			username: "only-username",
		});

		const response = await POST(request);
		const body = await response.json();

		expect(response.status).toBe(400);
		expect(body).toEqual({
			error: {
				message: "Missing username or password",
				code: "VALIDATION_MISSING_FIELD",
				details: { fields: ["username", "password"] },
			},
		});

		expect(User.findOne).not.toHaveBeenCalled();
		expect(createSession).not.toHaveBeenCalled();
	});

	it("returns 500 when an unexpected error occurs", async () => {
		User.findOne.mockImplementation(() => {
			throw new Error("DB failure");
		});

		const request = createRequestStub({
			username: "branchuser",
			password: "secret-password",
		});

		const response = await POST(request);
		const body = await response.json();

		expect(response.status).toBe(500);
		expect(body).toEqual({
			error: {
				message: "Internal server error",
				code: "INTERNAL_SERVER_ERROR",
			},
		});

		expect(createSession).not.toHaveBeenCalled();
	});
});
