import bcrypt from "bcryptjs";

import User from "@/models/user";
import { getDb } from "@/lib/db";
import { getSession, createSession } from "@/lib/auth/session";
import { validateNewPassword } from "@/lib/auth/passwordPolicy";
import {
	withErrorHandling,
	json,
	badRequest,
	unauthorized,
} from "@/lib/api/errors";

export const dynamic = "force-dynamic";

const BCRYPT_SALT_ROUNDS = 12;

export const POST = withErrorHandling(
	async function POST(request) {
		const session = await getSession();

		if (!session) {
			throw unauthorized("AUTH_UNAUTHENTICATED", "Unauthorized");
		}

		let body;
		try {
			body = await request.json();
		} catch {
			throw badRequest("VALIDATION_INVALID_JSON", "Invalid request body");
		}

		if (!body || typeof body !== "object") {
			throw badRequest("VALIDATION_INVALID_BODY", "Invalid request body");
		}

		const { currentPassword, newPassword } = body;

		const missing = [];
		if (typeof currentPassword !== "string" || !currentPassword.trim()) {
			missing.push("currentPassword");
		}
		if (typeof newPassword !== "string" || !newPassword.trim()) {
			missing.push("newPassword");
		}

		if (missing.length > 0) {
			throw badRequest(
				"VALIDATION_MISSING_FIELD",
				"Missing currentPassword or newPassword",
				{ fields: missing },
			);
		}

		await getDb();

		const user = await User.findById(session.userId).exec();

		// Treat missing users like an invalid session (do not leak anything).
		if (!user) {
			throw unauthorized("AUTH_UNAUTHENTICATED", "Unauthorized");
		}

		// Defensive: if hash is missing, treat as invalid credentials.
		if (typeof user.passwordHash !== "string" || !user.passwordHash) {
			throw unauthorized("AUTH_INVALID_CREDENTIALS", "Invalid credentials");
		}

		const currentMatches = await bcrypt.compare(
			currentPassword,
			user.passwordHash,
		);

		if (!currentMatches) {
			throw unauthorized("AUTH_INVALID_CREDENTIALS", "Invalid credentials");
		}

		const policyCheck = validateNewPassword({
			newPassword,
			currentPassword,
		});

		if (!policyCheck.ok) {
			throw badRequest("VALIDATION_WEAK_PASSWORD", "Weak password", {
				...policyCheck.policy,
				reasons: policyCheck.reasons,
			});
		}

		const newHash = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);

		user.passwordHash = newHash;
		user.mustChangePassword = false;

		// Defense-in-depth: invalidate any reset token when password changes.
		user.passwordResetToken = null;
		user.passwordResetExpiresAt = null;

		await user.save();

		// Refresh the session cookie so frontend revalidation sees the cleared flag
		// immediately (no logout/login roundtrip required).
		await createSession({
			userId: user._id.toString(),
			role: user.role ?? session.role,
			branchId: user.branchId ?? session.branchId ?? null,
			email: user.email ?? session.email ?? null,
			mustChangePassword: false,
		});

		return json({ ok: true }, 200);
	},
	{ logPrefix: "[api/auth/change-password]" },
);
