import { MongoClient } from "mongodb";
import { hash, compare } from "bcrypt";
import { prerendering } from "$app/environment";
import { randomBytes, randomUUID } from "crypto";
import { INVITE_KEY_PREFIX, SESSION_EXPIRE } from "$lib/constants";

if (!process.env.MONGO_URL) throw new Error("Missing MONGO_URL env variable!");

const client = new MongoClient(process.env.MONGO_URL);
if (!prerendering) await client.connect();

const db = await client.db("rowblox");
const users = await db.collection("users");
const games = await db.collection("games");
const assets = await db.collection("assets");
const invites = await db.collection("invites");
const avatars = await db.collection("avatars");
const sessions = await db.collection("sessions");
const friends = await db.collection("friends");

async function inc(collection) {
	await collection.updateOne({ _id: "_inc" }, { $inc: { _inc: 1 } });
	return (await collection.findOne({ _id: "_inc" }))._inc;
}

async function hashPassword(plaintext) {
	return new Promise((resolve, reject) => {
		hash(plaintext, 10, (err, hash) => resolve(hash));
	});
}

export async function compareHash(plaintext, hash) {
	return new Promise((resolve, reject) => {
		compare(plaintext, hash, (err, result) => resolve(result));
	});
}

function genSession() {
	return randomBytes(64).toString("base64");
}

function genInvite() {
	return `${INVITE_KEY_PREFIX}${randomUUID()}`;
}

export async function createUser(username, password, lastip) {
	const _id = await inc(users);

	const user = {
		_id,
		username,
		password: await hashPassword(password),
		lastip,
		currency: 100,
		joinDate: Date.now(),
		lastOnline: Date.now(),
		rank: 1,
		activity: 1,
		pendingFriendRequests: [],
		bio: "This is my Rowblox profile!"
	};

	const avatar = {
		_id,
		BodyColors: {
			HeadColor: 1,
			TorsoColor: 1,
			LeftArmColor: 1,
			RightArmColor: 1,
			LeftLegColor: 1,
			RightLegColor: 1
		},
		Wearing: [],
		Inventory: []
	};

	await Promise.all([avatars.insertOne(avatar), await users.insertOne(user)]);

	return _id;
}

export async function createSession(_id, lastip) {
	const user = await users.findOne({ _id });
	if (!user) return false;

	const session = genSession();

	await sessions.insertOne({
		_id: session,
		owner: _id,
		expires: Date.now() + SESSION_EXPIRE
	});

	await users.updateOne({ _id: user._id }, { $set: { lastip } });

	return session;
}

export async function getUserFromSession(session, lastip) {
	const sessionDocument = await sessions.findOne({ _id: session });
	if (!sessionDocument) return false;

	const user = await users.findOne({ _id: sessionDocument.owner });
	if (!user) return false;

	await users.updateOne({ _id: user._id }, { $set: { lastip } });

	return user;
}

export async function deleteSession(session, lastip) {
	const sessionDocument = await sessions.findOne({ _id: session });
	if (!sessionDocument) return false;

	const user = await users.findOne({ _id: sessionDocument.owner });
	if (!user) return false;

	await users.updateOne({ _id: user._id }, { $set: { lastip } });

	return await sessions.deleteOne({ _id: session });
}

export async function getUser(query, projection) {
	const user = await users.findOne(query, { projection });
	if (!user) return false;

	return user;
}
