// .env Variables
require("dotenv").config({path: "../.env"});

//socket.io setup
let server = require("http").createServer();
let io = require("socket.io")(server);
let ioAuth = require("socketio-auth");

ioAuth(io, {
	authenticate: function(socket, data, callback) {
		return callback(null, data.SERVER_PASS_KEY === process.env.SERVER_PASS_KEY);
	},
	postAuthenticate: function(socket, data) {
		console.log("Authenticated: " + data.username);
		socket.client.username = data.username;
	}
});

//mysql
let mysql = require("mysql");

let dbConnection = mysql.createConnection({
	host: process.env.DATABASE_HOST,
	user: process.env.DATABASE_USER,
	password: process.env.DATABASE_PASSWORD
});

dbConnection.connect((err) => {
	if (err) throw err;
	console.log("Connected to the database");
	dbConnection.query("USE sanctum;");
});

//shared code
let calcRandom = require('../Shared/calc_random');

//TODO: isolate these responses to specific bots
io.on("connection", async (socket) => {
	console.log("made socket connection");

	socket.on("disconnect", async () => {
		console.log(socket.client.username + " disconnected");
	});

	socket.on("updateStamina", handleUpdateStamina);
	socket.on("conversion", handleConversion);
	socket.on("checkin", handleCheckin);
	socket.on("wallet", handleWallet); //TODO: server ping from ADAM
	socket.on("transfer", handleTransfer);
	socket.on("userStats", handleUserStats);
	socket.on("addXP", handleAddXP);
	socket.on("levelUp", handleLevelUp);
});

//listen
server.listen(process.env.SERVER_PORT);
console.log("listening on port " + process.env.SERVER_PORT);

//update the playerbase's stamina on command
async function handleUpdateStamina({ userID, data }) {
	let query = "UPDATE users SET stamina = stamina + 1 WHERE stamina < maxStamina;";
	dbConnection.query(query, (err, result) => {
		if (err) throw err;
//		console.log("updated stamina for all users");
	});
};

//handle initial faction join and faction conversions
async function handleConversion({ data }, fn) {
	//data[0] = user ID
	//data[1] = factionRole

	//possible arguments to fn: ["joined", "alreadyJoined", "conversionLocked", "newUser"]

	//find the last time this user converted
	let query = `SELECT faction FROM users WHERE userID='${data[0]}' LIMIT 1;`;

	return dbConnection.query(query, (err, result) => {
		if (err) throw err;

		//check if this is a new user
		if (result.length === 0) {
			let query = `INSERT INTO users (userID, faction, factionChanged) VALUES (${data[0]}, ${data[1]}, NOW());`;
			return dbConnection.query(query, (err, result) => {
				if (err) throw err;
				dbLog(data[0], "new user", `joined faction ${data[1]}`);
				return fn("newUser");
			});
		}

		//check if already joined this faction
		if (result[0].faction == data[1]) { //faction == factionRole
			return fn("alreadyJoined");
		}

		//check if enough time has passed to join a new faction
		let query = `SELECT TIME_TO_SEC(TIMEDIFF(NOW(), factionChanged)) FROM users WHERE userID='${data[0]}' LIMIT 1;`;

		return dbConnection.query(query, (err, result) => {
			if (err) throw err;
			if(result[0]['TIME_TO_SEC(TIMEDIFF(NOW(), factionChanged))'] < 60 * 60 * 24 * 7) { //7 days
				return fn("conversionLocked"); //too soon
			} else {
				//update the database with the join
				query = `UPDATE users SET faction = ${data[1]}, factionChanged = NOW() WHERE userID='${data[0]}';`;
				return dbConnection.query(query, (err, result) => {
					if (err) throw err;
					dbLog(data[0], "joined", `joined faction ${data[1]}`);
					return fn("joined");
				});
			}
		})
	});
}

//handle checkin, and add 1 XP
async function handleCheckin({ data }, fn) {
	//handle checkins (grant crystal bonus)

	//arguments to fn: ["available", time since last checkin], randomAmount

	let randomAmount = calcRandom.Random(4, 9);

	let query = `SELECT TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin)) FROM users WHERE userID='${data[0]}' LIMIT 1;`;

	return dbConnection.query(query, (err, result) => {
		if (err) throw err;

		if (result[0]['TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin))'] == null || result[0]['TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin))'] > 60 * 60 * 22) { //22 hours
			let query = `UPDATE users SET lastCheckin = NOW(), wallet = wallet + ${randomAmount} WHERE userID='${data[0]}' LIMIT 1;`;
			return dbConnection.query(query, (err, result) => {
				if (err) throw err;
				dbLog(data[0], "checkin", `gained ${randomAmount} to wallet`);
				addExperience(data[0], 1); //Add 1 XP on every checkin
				return fn("available", randomAmount);
			});
		} else {
			return fn(calculateTimeAgo(result[0]['TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin))']));
		}
	});
}

//handle account requests
async function handleWallet({ data }, fn) {
	//data[0] = ID of the person to check

	let query = `SELECT wallet FROM users WHERE userID='${data[0]}' LIMIT 1;`;
	dbConnection.query(query, (err, result) => {
		if (err) throw err;
		dbLog(data[0], "wallet query", `result: ${result[0].wallet}`);
		fn(result[0].wallet);
	});
}

//handle transfering wallet balance between accounts
async function handleTransfer({ data }, fn) {
	console.log("received a transfer request...");
	//data[0] = ID of the source account
	//data[1] = ID of the destination account
	//data[2] = amount to send

	//parameters to fn: ["success", "failure"]

	//check there's enough in the sender's wallet
	let query = `SELECT wallet - ${data[2]} FROM users WHERE userID='${data[0]}' LIMIT 1;`;
	return dbConnection.query(query, (err, result) => {
		if (err) throw err;

		//too little in there
		if (result[0][`wallet - ${data[2]}`] < 0) {
			return fn("failure");
		}

		//check the recipient is real
		let query = `SELECT * FROM users WHERE userID='${data[1]}' LIMIT 1;`;
		return dbConnection.query(query, (err, result) => {
			if (err) throw err;

			if (result.length == 0) {
				return fn("failure");
			}

			//subtract from the sender
			let query = `UPDATE users SET wallet = wallet - ${data[2]} WHERE userID='${data[0]}' LIMIT 1;`;
			return dbConnection.query(query, (err, result) => {
				if (err) throw err;

				//add to the recipient
				let query = `UPDATE users SET wallet = wallet + ${data[2]} WHERE userID='${data[1]}' LIMIT 1;`;
				return dbConnection.query(query, (err, result) => {
					if (err) throw err;

					//finally
					dbLog(data[0], "wallet transfer", `${data[2]} to ${data[1]}`);
					return fn("success");
				});
			});
		});
	});
}

//handle the user stats
async function handleUserStats({ data }, fn) {
	console.log("received a userStats request...");
	//data[0] = user ID

	//parameters to fn: stat structure

	let query = `SELECT level, experience, maxHealth, health, maxStamina, stamina, strength, speed, upgradePoints, wallet FROM users WHERE userID='${data[0]}' LIMIT 1;`;
	return dbConnection.query(query, (err, result) => {
		if (err) throw err;

		let stats = {
			level: result[0].level,
			experience: result[0].experience,
			levelProgress: calculateLevelProgress(result[0].experience),
			maxHealth: result[0].maxHealth,
			health: result[0].health,
			maxStamina: result[0].maxStamina,
			stamina: result[0].stamina,
			strength: result[0].strength,
			speed: result[0].speed,
			upgradePoints: result[0].upgradePoints,
			wallet: result[0].wallet
		};

		return fn(stats);
	});
}

//DEBUGGING?
async function handleAddXP({ userID, data }) {
	console.log("received an addXP request...");
	//data[0] = amount

	addExperience(userID, data[0]);
}

//handle levelling up
async function handleLevelUp({ data }, fn) {
	//NOTE: levelling up is handled manually because of reasons
	console.log("received a levelUp request...");
	//data[0] = user ID

	//parameters to fn: ["none", "levelUp"], level, upgradePoints

	//get the current level and total amount of experience
	let query = `SELECT level, experience, upgradePoints FROM users WHERE userID='${data[0]}' LIMIT 1;`;
	return dbConnection.query(query, (err, result) => {
		if (err) throw err;

		//calculate the correct level, and compare it with the result
		let newLevel = Math.floor(calculateLevel(result[0].experience));

		//if no levelling, return
		if (newLevel == result[0].level) {
			return fn("none", result[0].level, result[0].upgradePoints);
		}

		//handle the level cap
		if (newLevel >= process.env.RANK_3_THRESHOLD) {
			//update the level, add lootbox
			//TODO: add lootbox item
			let query = `UPDATE users SET level = ${newLevel} WHERE userID='${data[0]}' LIMIT 1;`;
			return dbConnection.query(query, (err, result) => {
				if (err) throw err;

				//finally, pass the level and upgrade points to the client
				let query = `SELECT level, upgradePoints FROM users WHERE userID='${data[0]}' LIMIT 1;`;
				return dbConnection.query(query, (err, result) => {
					if (err) throw err;
					dbLog(data[0], "level max", `level: ${result[0].level}, upgrade points: ${result[0].upgradePoints}, lootboxes: ???`);
					return fn("levelUp", result[0].level, result[0].upgradePoints);
				});
			});
		}

		//update the level and the upgrade points
		let query = `UPDATE users SET level = ${newLevel}, upgradePoints = upgradePoints + ${newLevel - result[0].level} WHERE userID='${data[0]}' LIMIT 1;`;
		return dbConnection.query(query, (err, result) => {
			if (err) throw err;

			//finally, pass the level and upgrade points to the client
			let query = `SELECT level, upgradePoints FROM users WHERE userID='${data[0]}' LIMIT 1;`;
			return dbConnection.query(query, (err, result) => {
				if (err) throw err;
				dbLog(data[0], "level up", `level: ${result[0].level}, upgrade points: ${result[0].upgradePoints}`);
				return fn("levelUp", result[0].level, result[0].upgradePoints);
			});
		});
	});
}

//utility functions
function calculateLevel(experience) {
	const levelBase = 1.2;
	return levelBase * Math.sqrt(experience);
}

function calculateLevelProgress(experience) {
	let level = calculateLevel(experience);

	let base = Math.floor(level);
	let decimal = level - base;

	return Math.floor(decimal * 100); //percentage
}

function addExperience(userID, amount) {
	//Add an amount of XP to a user account
	let query = `UPDATE users SET experience = experience + ${amount} WHERE userID='${userID}' LIMIT 1;`;
	return dbConnection.query(query, (err, result) => {
		if (err) throw err;
		dbLog(userID, "xp up", `amount added: ${amount}`);
	});
}

function dbLog(id, type, data) {
	let query = `INSERT INTO log (discordID, type, data) VALUES ('${id}', '${type}', '${data}')`;
	return dbConnection.query(query, (err, result) => {
		if (err) throw err;
	});
}

function calculateTimeAgo(seconds) {
	if (seconds < 60) {
		return "just now";
	}

	if (seconds < 60 * 60) {
		return "this hour";
	}

	if (seconds < 60 * 60 * 24) {
		return "today";
	}

	return "recently";
}