//environment variables
require('dotenv').config();

//libraries
let CronJob = require('cron').CronJob;

//utilities
let { log } = require('../common/utilities.js');

const attackRequest = (connection) => (req, res) => {
	//verify the attacker's credentials
	let query = 'SELECT accountId FROM sessions WHERE accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;';
	connection.query(query, [req.body.attacker, req.body.token], (err, results) => {
		if (err) throw err;

		if (results.length !== 1) {
			res.status(400).write(log('Invalid attack credentials', req.body.attacker, req.body.defender, req.body.token));
			res.end();
			return;
		}

		let attackerId = results[0].accountId;

		//verify that the defender exists
		let query = 'SELECT id FROM accounts WHERE username = ?;';
		connection.query(query, [req.body.defender], (err, results) => {
			if (err) throw err;

			if (results.length !== 1) {
				res.status(400).write(log('Invalid defender credentials', req.body.attacker, req.body.defender));
				res.end();
				return;
			}

			let defenderId = results[0].id;

			//verify that the attacker has enough soldiers
			let query = 'SELECT soldiers FROM profiles WHERE accountId = ?;';
			connection.query(query, [attackerId], (err, results) => {
				if (err) throw err;

				if (results[0].soldiers <= 0) {
					res.status(400).write(log('Not enough soldiers', req.body.attacker, req.body.defender, results[0].soldiers));
					res.end();
					return;
				}

				let attackingUnits = results[0].soldiers;

				//verify that the attacker is not already attacking someone
				isAttacking(connection, req.body.attacker, (isAttacking) => {
					if (isAttacking) {
						res.status(400).write(log('You are already attacking someone', req.body.attacker, req.body.defender));
						res.end();
						return;
					}

					//create the pending attack value
					let query = 'INSERT INTO pendingCombat (eventTime, attackerId, defenderId, attackingUnits) VALUES (DATE_ADD(CURRENT_TIMESTAMP(), INTERVAL 60 * ? SECOND), ?, ?, ?);';
					connection.query(query, [attackingUnits, attackerId, defenderId, attackingUnits], (err) => {
						if (err) throw err;

						res.status(200).json({
							status: 'attacking',
							defender: req.body.defender
						});
						res.end();

						log(`attacking ${req.body.defender}`, req.body.attacker, req.body.defender)
					});
				});
			});
		});
	});
}

const attackStatusRequest = (connection) => (req, res) => {
	isAttacking(connection, req.body.username, (isAttacking, defender) => {
		res.status(200).json({
			status: log(isAttacking ? 'attacking' : 'idle', req.body.username, defender),
			defender: defender
		});

		res.end();
	});
}

const combatLogRequest = (connection) => (req, res) => {
	let query = 'SELECT pastCombat.*, atk.username AS attackerUsername, def.username AS defenderUsername FROM pastCombat JOIN accounts AS atk ON pastCombat.attackerId = atk.id JOIN accounts AS def ON pastCombat.defenderId = def.id WHERE def.username = ? ORDER BY eventTime DESC LIMIT ?, ?;';
	connection.query(query, [req.body.username, req.body.start, req.body.length], (err, results) => {
		if (err) throw err;

		res.status(200).json(results);
		log('Combat log sent', req.body.username, req.body.start, req.body.length, JSON.stringify(results));
	});
}

const runCombatTick = (connection) => {
	let combatTick = new CronJob('* * * * * *', () => {
		//find each pending combat
		let query = 'SELECT * FROM pendingCombat WHERE eventTime < CURRENT_TIMESTAMP();';
		connection.query(query, (err, results) => {
			if (err) throw err;

			results.forEach((pendingCombat) => {
				//get the defender's undefended status
				isAttacking(connection, pendingCombat.defenderId, (undefended) => {
					//get the defending unit count, gold
					let query = 'SELECT soldiers, recruits, gold FROM profiles WHERE accountId = ?;';

					connection.query(query, [pendingCombat.defenderId], (err, results) => {
						if (err) throw err;

						let defendingUnits;
						if (!undefended && results[0].soldiers > 0) {
							defendingUnits = results[0].soldiers;
						} else {
							defendingUnits = results[0].recruits;
						}

						//determine the victor
						let rand = Math.random() * (pendingCombat.attackingUnits + defendingUnits * (undefended ? 0.25 : 1));
						let victor = rand <= pendingCombat.attackingUnits ? 'attacker' : 'defender';

						//determine the spoils and casualties
						let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02));
						let casualtiesVictor = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits - 10 : 0) * (victor === 'attacker' ? 0.05 : 0.1));

						//save the combat
						let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);';
						connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor], (err) => {
							if (err) throw err;

							//update the attacker profile
							let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE id = ?;';
							connection.query(query, [spoilsGold, casualtiesVictor, pendingCombat.attackerId], (err) => {
								if (err) throw err;

								//update the defender profile
								let query = 'UPDATE profiles SET gold = gold - ? WHERE id = ?;';
								connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => {
									if (err) throw err;

									//delete the pending combat
									let query = 'DELETE FROM pendingCombat WHERE id = ?;';
									connection.query(query, [pendingCombat.id], (err) => {
										if (err) throw err;

										log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor);
									});
								});
							});
						});
					});
				});
			});
		});
	});

	combatTick.start();
}

const isAttacking = (connection, user, cb) => {
	let query;

	if (typeof(user) === 'string') {
		query = 'SELECT * FROM pendingCombat WHERE attackerId IN (SELECT id FROM accounts WHERE username = ?);';
	} else if (typeof(user) === 'number') {
		query = 'SELECT * FROM pendingCombat WHERE attackerId = ?;';
	}

	connection.query(query, [user], (err, results) => {
		if (err) throw err;

		if (results.length === 0) {
			cb(false);
		} else {
			//get the username of the person being attacked
			let query = 'SELECT username FROM accounts WHERE id = ?;';
			connection.query(query, [results[0].defenderId], (err, results) => {
				if (err) throw err;
				cb(true, results[0].username);
			});
		}
	});
}

module.exports = {
	attackRequest: attackRequest,
	attackStatusRequest: attackStatusRequest,
	combatLogRequest: combatLogRequest,
	runCombatTick: runCombatTick
}

/*
> You can attack another player using your soldiers (it doesn't work without soldiers).
> Doing so takes time, up to 10 seconds for every soldier you have.
> Combat takes place at the end of the time delay, at which point you can attack people again (after reloading the page).
> While attacking, you are undefended.
> While undefended, your recruits act as combatants, otherwise your soldiers do.
> The chance of success is determined by the ratio of each side's combatant strength.
> Recruits have a strength equal to 0.25 times that of a soldier.
> On a success, you steal 10% of the target's gold. On a failure, you steal 2% of the target's gold.
> The attacking force will lose a percentage, rounded down, of their units - 5% on a success, 10% on a failure (edit: excluding the first 10 units).
> If the server resets (which happens alot) combat still progresses as expected.
* All combat is logged and presented to the player.
*/

