import bcrypt from "bcryptjs";

import User, { USER_ROLES } from "@/models/user";
import { getDb } from "@/lib/db";
import { getSession } from "@/lib/auth/session";
import { requireUserManagement } from "@/lib/auth/permissions";
import { validateNewPassword } from "@/lib/auth/passwordPolicy";
import {
	ADMIN_USERS_SORT,
	normalizeAdminUsersSortMode,
	sortAdminUsers,
} from "@/lib/frontend/admin/users/usersSorting";
import {
	withErrorHandling,
	json,
	badRequest,
	unauthorized,
} from "@/lib/api/errors";

export const dynamic = "force-dynamic";

const BRANCH_RE = /^NL\d+$/;
const OBJECT_ID_RE = /^[a-f0-9]{24}$/i;

const USERNAME_RE = /^[a-z0-9][a-z0-9._-]{2,31}$/; // 3..32, conservative
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const BCRYPT_SALT_ROUNDS = 12;

const DEFAULT_LIMIT = 50;
const MIN_LIMIT = 1;
const MAX_LIMIT = 200;

function isPlainObject(value) {
	return Boolean(value && typeof value === "object" && !Array.isArray(value));
}

function isNonEmptyString(value) {
	return typeof value === "string" && value.trim().length > 0;
}

function escapeRegExp(input) {
	return String(input).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function normalizeUsername(value) {
	return String(value || "")
		.trim()
		.toLowerCase();
}

function normalizeEmail(value) {
	return String(value || "")
		.trim()
		.toLowerCase();
}

function normalizeBranchId(value) {
	return String(value || "")
		.trim()
		.toUpperCase();
}

function parseLimitOrThrow(raw) {
	if (raw === null || raw === undefined || raw === "") return DEFAULT_LIMIT;

	const s = String(raw).trim();
	if (!/^\d+$/.test(s)) {
		throw badRequest("VALIDATION_INVALID_FIELD", "Invalid limit parameter", {
			field: "limit",
			value: raw,
			min: MIN_LIMIT,
			max: MAX_LIMIT,
		});
	}

	const n = Number(s);
	if (!Number.isInteger(n)) {
		throw badRequest("VALIDATION_INVALID_FIELD", "Invalid limit parameter", {
			field: "limit",
			value: raw,
			min: MIN_LIMIT,
			max: MAX_LIMIT,
		});
	}

	return Math.min(MAX_LIMIT, Math.max(MIN_LIMIT, n));
}

function encodeCursor(payload) {
	if (!isPlainObject(payload)) {
		throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
			field: "cursor",
		});
	}

	const v = payload.v;
	if (v === 1) {
		const lastId = payload.lastId;
		if (typeof lastId !== "string" || !OBJECT_ID_RE.test(lastId)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}
		return Buffer.from(JSON.stringify({ v: 1, lastId }), "utf8").toString(
			"base64url",
		);
	}

	if (v === 2) {
		const sort = normalizeAdminUsersSortMode(payload.sort);
		const offset = payload.offset;

		if (sort === null || sort === ADMIN_USERS_SORT.DEFAULT) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}
		if (!Number.isInteger(offset) || offset < 0) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}

		return Buffer.from(JSON.stringify({ v: 2, sort, offset }), "utf8").toString(
			"base64url",
		);
	}

	throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
		field: "cursor",
	});
}

function decodeCursorOrThrow(raw, expectedSort) {
	if (raw === null || raw === undefined || String(raw).trim() === "") {
		return null;
	}

	const sort = normalizeAdminUsersSortMode(expectedSort);
	if (sort === null) {
		throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
			field: "cursor",
		});
	}

	const s = String(raw).trim();

	let decoded;
	try {
		decoded = Buffer.from(s, "base64url").toString("utf8");
	} catch {
		throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
			field: "cursor",
		});
	}

	let parsed;
	try {
		parsed = JSON.parse(decoded);
	} catch {
		throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
			field: "cursor",
		});
	}

	if (!isPlainObject(parsed) || parsed.v !== 1) {
		// continue with shape-specific validation below
	}

	if (parsed?.v === 1) {
		const lastId = parsed.lastId;
		const parsedSort = normalizeAdminUsersSortMode(parsed.sort);
		const effectiveSort = parsedSort ?? ADMIN_USERS_SORT.DEFAULT;

		if (sort !== effectiveSort) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}

		if (sort !== ADMIN_USERS_SORT.DEFAULT) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}

		if (typeof lastId !== "string" || !OBJECT_ID_RE.test(lastId)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}

		return { lastId };
	}

	if (parsed?.v === 2) {
		const parsedSort = normalizeAdminUsersSortMode(parsed.sort);
		const offset = parsed.offset;

		if (parsedSort === null || parsedSort !== sort) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}
		if (!Number.isInteger(offset) || offset < 0) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
				field: "cursor",
			});
		}

		return { offset };
	}

	throw badRequest("VALIDATION_INVALID_FIELD", "Invalid cursor", {
		field: "cursor",
	});
}

function toIsoOrNull(value) {
	if (!value) return null;
	try {
		return new Date(value).toISOString();
	} catch {
		return null;
	}
}

function toSafeUser(doc) {
	return {
		id: String(doc?._id),
		username: typeof doc?.username === "string" ? doc.username : "",
		email: typeof doc?.email === "string" ? doc.email : "",
		role: typeof doc?.role === "string" ? doc.role : "",
		branchId: doc?.branchId ?? null,
		mustChangePassword: Boolean(doc?.mustChangePassword),
		createdAt: toIsoOrNull(doc?.createdAt),
		updatedAt: toIsoOrNull(doc?.updatedAt),
	};
}

function pickDuplicateField(err) {
	if (!err || typeof err !== "object") return null;

	const keyValue =
		err.keyValue && typeof err.keyValue === "object" ? err.keyValue : null;
	if (keyValue) {
		const keys = Object.keys(keyValue);
		if (keys.length > 0) return keys[0];
	}

	const keyPattern =
		err.keyPattern && typeof err.keyPattern === "object"
			? err.keyPattern
			: null;
	if (keyPattern) {
		const keys = Object.keys(keyPattern);
		if (keys.length > 0) return keys[0];
	}

	return null;
}

const ALLOWED_ROLES = new Set(Object.values(USER_ROLES));

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

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

		requireUserManagement(session);

		const { searchParams } = new URL(request.url);

		const qRaw = searchParams.get("q");
		const q =
			typeof qRaw === "string" && qRaw.trim().length > 0 ? qRaw.trim() : null;

		const roleRaw = searchParams.get("role");
		const role =
			typeof roleRaw === "string" && roleRaw.trim().length > 0
				? roleRaw.trim()
				: null;

		const branchIdRaw = searchParams.get("branchId");
		const branchId =
			typeof branchIdRaw === "string" && branchIdRaw.trim().length > 0
				? branchIdRaw.trim()
				: null;

		const sortRaw = searchParams.get("sort");
		const sort = normalizeAdminUsersSortMode(sortRaw);
		if (sort === null) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid sort", {
				field: "sort",
				value: sortRaw,
				allowed: Object.values(ADMIN_USERS_SORT),
			});
		}

		const limit = parseLimitOrThrow(searchParams.get("limit"));
		const cursor = decodeCursorOrThrow(searchParams.get("cursor"), sort);

		if (role && !ALLOWED_ROLES.has(role)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid role", {
				field: "role",
				value: role,
				allowed: Array.from(ALLOWED_ROLES),
			});
		}

		if (branchId && !BRANCH_RE.test(branchId)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid branchId", {
				field: "branchId",
				value: branchId,
				pattern: "^NL\\d+$",
			});
		}

		const filter = {};

		if (q) {
			const re = new RegExp(escapeRegExp(q), "i");
			filter.$or = [{ username: { $regex: re } }, { email: { $regex: re } }];
		}

		if (role) filter.role = role;
		if (branchId) filter.branchId = branchId;

		await getDb();

		let docs = [];
		let nextCursor = null;

		if (sort === ADMIN_USERS_SORT.DEFAULT) {
			if (cursor?.lastId) filter._id = { $lt: cursor.lastId };

			const rawDocs = await User.find(filter)
				.sort({ _id: -1 })
				.limit(limit + 1)
				.select(
					"_id username email role branchId mustChangePassword createdAt updatedAt",
				)
				.exec();

			const list = Array.isArray(rawDocs) ? rawDocs : [];
			const hasMore = list.length > limit;
			docs = hasMore ? list.slice(0, limit) : list;

			nextCursor =
				hasMore && docs.length > 0
					? encodeCursor({ v: 1, lastId: String(docs[docs.length - 1]._id) })
					: null;
		} else {
			const rawDocs = await User.find(filter)
				.select(
					"_id username email role branchId mustChangePassword createdAt updatedAt",
				)
				.exec();

			const sorted = sortAdminUsers(
				Array.isArray(rawDocs) ? rawDocs : [],
				sort,
			);

			const offset = cursor?.offset ?? 0;
			docs = sorted.slice(offset, offset + limit);
			const hasMore = offset + docs.length < sorted.length;

			nextCursor = hasMore
				? encodeCursor({
						v: 2,
						sort,
						offset: offset + docs.length,
					})
				: null;
		}

		return json(
			{
				items: docs.map(toSafeUser),
				nextCursor,
			},
			200,
		);
	},
	{ logPrefix: "[api/admin/users]" },
);

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

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

		requireUserManagement(session);

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

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

		const usernameRaw = body.username;
		const emailRaw = body.email;
		const roleRaw = body.role;
		const branchIdRaw = body.branchId;
		const initialPasswordRaw = body.initialPassword;

		const role =
			typeof roleRaw === "string" && roleRaw.trim() ? roleRaw.trim() : null;

		const missing = [];
		if (!isNonEmptyString(usernameRaw)) missing.push("username");
		if (!isNonEmptyString(emailRaw)) missing.push("email");
		if (!isNonEmptyString(roleRaw)) missing.push("role");
		if (!isNonEmptyString(initialPasswordRaw)) missing.push("initialPassword");

		// branchId required iff role is branch
		if (role === USER_ROLES.BRANCH) {
			if (!isNonEmptyString(branchIdRaw)) missing.push("branchId");
		}

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

		if (!ALLOWED_ROLES.has(role)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid role", {
				field: "role",
				value: role,
				allowed: Array.from(ALLOWED_ROLES),
			});
		}

		const username = normalizeUsername(usernameRaw);
		if (!USERNAME_RE.test(username)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid username", {
				field: "username",
				value: username,
				pattern: String(USERNAME_RE),
			});
		}

		const email = normalizeEmail(emailRaw);
		if (!EMAIL_RE.test(email)) {
			throw badRequest("VALIDATION_INVALID_FIELD", "Invalid email", {
				field: "email",
				value: email,
			});
		}

		let branchId = null;
		if (role === USER_ROLES.BRANCH) {
			branchId = normalizeBranchId(branchIdRaw);
			if (!BRANCH_RE.test(branchId)) {
				throw badRequest("VALIDATION_INVALID_FIELD", "Invalid branchId", {
					field: "branchId",
					value: branchId,
					pattern: "^NL\\d+$",
				});
			}
		}

		const initialPassword = String(initialPasswordRaw);

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

		await getDb();

		// Uniqueness checks (return one field OR both fields)
		const [existingUsername, existingEmail] = await Promise.all([
			User.findOne({ username }).select("_id").exec(),
			User.findOne({ email }).select("_id").exec(),
		]);

		const duplicateFields = [];
		if (existingUsername) duplicateFields.push("username");
		if (existingEmail) duplicateFields.push("email");

		if (duplicateFields.length === 1) {
			const field = duplicateFields[0];

			throw badRequest(
				"VALIDATION_INVALID_FIELD",
				field === "username"
					? "Username already exists"
					: "Email already exists",
				{ field },
			);
		}

		if (duplicateFields.length > 1) {
			throw badRequest(
				"VALIDATION_INVALID_FIELD",
				"Username and email already exist",
				{ fields: duplicateFields },
			);
		}

		const passwordHash = await bcrypt.hash(initialPassword, BCRYPT_SALT_ROUNDS);

		try {
			const created = await User.create({
				username,
				email,
				passwordHash,
				role,
				branchId,
				mustChangePassword: true,
				passwordResetToken: null,
				passwordResetExpiresAt: null,
			});

			return json({ ok: true, user: toSafeUser(created) }, 200);
		} catch (err) {
			// Race-condition safe duplicate mapping
			if (err && err.code === 11000) {
				const field = pickDuplicateField(err) || "unknown";
				throw badRequest("VALIDATION_INVALID_FIELD", "Duplicate key", {
					field,
				});
			}

			throw err;
		}
	},
	{ logPrefix: "[api/admin/users]" },
);
