const express = require("express");
const session = require("express-session");
const mysql2 = require('mysql2');
const bodyParser = require("body-parser");
const bcrypt = require("bcrypt");
const crypto = require("crypto");
const nodemailer = require("nodemailer");

const { transporter } = require("./modules/nodeMailer");
const { connection } = require("./modules/mysql"); 

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const PORT = process.env.PORT || 3000;
require("dotenv").config();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(
	session({
		secret: "your_session_secret",
		resave: false,
		saveUninitialized: true,
	})
);
app.set("view engine", "ejs");

function isAuthenticated(req, res, next) {
	if (req.session && req.session.authenticated) {
		return next();
	} else {
		res.redirect("/login");
	}
}

app.get("/login", (req, res) => {
	// Pass an initial value for the error variable
	res.render("login", { error: null });
});

const logActivity = async (username, success) => {
	try {
		const activity = success
			? "successful login"
			: "unsuccessful login due to invalid password or username";
		const logSql =
			"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
		const logParams = [username, activity];

		connection.query(logSql, logParams, (error, results) => {
			if (error) {
				console.error("Error logging activity:", error);
				// Handle error (you may want to log it or take other appropriate actions)
			} else {
				console.log("Activity logged successfully");
			}

		});
	} catch (error) {
		console.error("Error in logActivity function:", error);
		// Handle error (you may want to log it or take other appropriate actions)
	}
};

app.post("/login", async (req, res) => {
	try {
		let { username, password } = req.body;
		username = username.trim();

		const loginSql = "SELECT * FROM users WHERE username = ?";
		const updateLastLoginSql =
			"UPDATE users SET lastLogin = CURRENT_TIMESTAMP WHERE username = ?";

		console.log("Login Query:", loginSql);
		console.log("Query Parameters:", [username]);

		connection.query(loginSql, [username], async (error, results) => {
			console.log("Login Results:", results);

			if (error) {
				console.error("Error executing login query:", error);
				res.status(500).send("Internal Server Error");
				return;
			}

			const isLoginSuccessful =
				results.length > 0 &&
				(await bcrypt.compare(password, results[0].password));

			// Log login attempt
			await logActivity(username, isLoginSuccessful);

			if (isLoginSuccessful) {
				const user = results[0];

				// Update lastLogin field for the user
				connection.query(
					updateLastLoginSql,
					[username],
					(updateError, updateResults) => {
						if (updateError) {
							console.error("Error updating lastLogin:", updateError);
							res.status(500).send("Internal Server Error");
							return;
						}

						// Check if the update affected any rows
						if (updateResults.affectedRows > 0) {
							// Set session data for authentication
							req.session.regenerate((err) => {
								if (err) {
									console.error("Error regenerating session:", err);
								}
								console.log("Session regenerated successfully");
								req.session.authenticated = true;
								req.session.username = username;
								res.redirect("/home");
							});
						} else {
							// Pass the error to the template
							res.render("login", {
								error: "Error updating lastLogin. No rows affected.",
							});
						}
					}
				);
			} else {
				// Pass the error to the template
				res.render("login", { error: "Invalid username or password" });
			}
		});
	} catch (error) {
		console.error("Error in login route:", error);
		res.status(500).send("Internal Server Error");
	}
});

// Update your /home route to retrieve the overall last 10 logins for all users
app.get("/home", isAuthenticated, (req, res) => {
	// Retrieve the overall last 10 logins for all users
	const loginsQuery =
		"SELECT username, lastLogin FROM users ORDER BY lastLogin DESC LIMIT 10";

	connection.query(loginsQuery, (error, loginResults) => {
		if (error) {
			console.error("Error executing login logs query:", error);
			res.status(500).send("Internal Server Error");
			return;
		}

		// Log the results on the server side
		console.log("Login Logs on Server:", loginResults);

		// Render the home page with login logs data
		res.render("home", {
			username: req.session.username,
			loginLogs: loginResults,
		});
	});
});
app.get("/inusers", isAuthenticated, (req, res) => {
	// Fetch all user data from the database
	const allUsersQuery = "SELECT * FROM users";

	connection.query(allUsersQuery, (error, allUsers) => {
		if (error) {
			console.error("Error fetching all users:", error);
			res.status(500).send("Internal Server Error");
			return;
		}

		// Render the inusers page with JSON data
		res.render("inusers", { allUsers });
	});
});
function isStrongPassword(password) {
	// Password must be at least 10 characters long
	if (password.length < 10) {
		return false;
	}

	// Password must contain at least one uppercase letter
	if (!/[A-Z]/.test(password)) {
		return false;
	}

	// Password must contain at least one lowercase letter
	if (!/[a-z]/.test(password)) {
		return false;
	}

	// Password must contain at least one digit
	if (!/\d/.test(password)) {
		return false;
	}

	// Password must contain at least one symbol
	if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
		return false;
	}

	return true;
}

const logUserCreationActivity = async (creatorUsername, success, message) => {
	try {
		const activity = success
			? "successful user creation"
			: `unsuccessful user creation: ${message}`;
		const logSql =
			"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
		const logParams = [creatorUsername, activity];

		connection.query(logSql, logParams, (error, results) => {
			if (error) {
				console.error("Error logging user creation activity:", error);
				// Handle error (you may want to log it or take other appropriate actions)
			} else {
				console.log("User creation activity logged successfully");
			}

		});
	} catch (error) {
		console.error("Error in logUserCreationActivity function:", error);
		// Handle error (you may want to log it or take other appropriate actions)
	}
};

app.post("/createUser", async (req, res) => {
	try {
		const { name, username, email, password, jobTitle } = req.body;

		// Extract the username of the user creating a new user
		const creatorUsername = req.session.username; // Adjust this based on how you store the creator's username in your session

		// Validate password complexity
		if (!isStrongPassword(password)) {
			return res
				.status(400)
				.json({ error: "Password does not meet complexity requirements" });
		}

		// Check if the username is already taken
		const checkUsernameQuery = "SELECT * FROM users WHERE username = ?";
		connection.query(
			checkUsernameQuery,
			[username],
			(usernameQueryErr, usernameResults) => {
				if (usernameQueryErr) {
					console.error("Error checking username:", usernameQueryErr);
					return res.status(500).json({ error: "Internal Server Error" });
				}

				if (usernameResults.length > 0) {
					// Log unsuccessful user creation due to username taken
					logUserCreationActivity(creatorUsername, false, "username taken");
					return res
						.status(400)
						.json({
							error: "Username is already taken",
							message:
								"Username is already taken. Please choose a different username.",
						});
				}

				// Check if the email is already taken
				const checkEmailQuery = "SELECT * FROM users WHERE email = ?";
				connection.query(
					checkEmailQuery,
					[email],
					(emailQueryErr, emailResults) => {
						if (emailQueryErr) {
							console.error("Error checking email:", emailQueryErr);
							return res.status(500).json({ error: "Internal Server Error" });
						}

						if (emailResults.length > 0) {
							// Log unsuccessful user creation due to email taken
							logUserCreationActivity(creatorUsername, false, "email taken");
							return res
								.status(400)
								.json({
									error: "Email is already in use",
									message:
										"Email is already in use. Please choose another email.",
								});
						}

						// Hash the password before storing it in the database
						bcrypt.hash(password, 10, (hashError, hashedPassword) => {
							if (hashError) {
								console.error("Error hashing password:", hashError);
								return res.status(500).json({ error: "Internal Server Error" });
							}

							// Start a transaction
							connection.beginTransaction((transactionErr) => {
								if (transactionErr) {
									console.error("Error starting transaction:", transactionErr);
									return res
										.status(500)
										.json({ error: "Internal Server Error" });
								}

								// Define the insert query
								const insertUserQuery =
									"INSERT INTO users (name, username, email, password, lastLogin, jobTitle) VALUES (?, ?, ?, ?, NULL, ?)";

								// Log the query and its parameters
								console.log("Insert Query:", insertUserQuery);
								console.log("Query Parameters:", [
									name,
									username,
									email,
									hashedPassword,
									jobTitle,
								]);

								// Execute the query with user data
								connection.query(
									insertUserQuery,
									[name, username, email, hashedPassword, jobTitle],
									(queryErr, results) => {
										if (queryErr) {
											console.error("Error executing query:", queryErr);

											// Rollback the transaction in case of an error
											connection.rollback((rollbackErr) => {
												if (rollbackErr) {
													console.error(
														"Error rolling back transaction:",
														rollbackErr
													);
												}
												// Log unsuccessful user creation due to an error
												logUserCreationActivity(
													creatorUsername,
													false,
													"internal error"
												);
												return res
													.status(500)
													.json({ error: "Internal Server Error" });
											});
											return;
										}

										// Commit the transaction
										connection.commit((commitErr) => {
											if (commitErr) {
												console.error(
													"Error committing transaction:",
													commitErr
												);
												// Log unsuccessful user creation due to an error
												logUserCreationActivity(
													creatorUsername,
													false,
													"internal error"
												);
												return res
													.status(500)
													.json({ error: "Internal Server Error" });
											}

											// Log successful user creation
											logUserCreationActivity(
												creatorUsername,
												true,
												"user created successfully"
											);

											// Log the results of the query
											console.log("Query Results:", results);

											// Respond with a success message
											res
												.status(201)
												.json({ message: "User created successfully" });
										});
									}
								);
							});
						});
					}
				);
			}
		);
	} catch (error) {
		console.error("Error creating user:", error);
		// Log unsuccessful user creation due to an error
		logUserCreationActivity(req.session.username, false, "internal error"); // Adjust this based on how you store the creator's username in your session
		res.status(500).json({ error: "Internal Server Error" });
	}
});

app.get("/forgot-password", (req, res) => {
	res.render("forgot-password"); // Assuming you have an EJS template for this
});

app.get("/forgot-password", (req, res) => {
	res.render("forgot-password", { error: null, success: null });
});

// Handle the submission of the forgot password form
app.post("/forgot-password", (req, res) => {
	const { usernameOrEmail } = req.body;

	// Perform the logic for sending the reset password email

	// Check if the username or email exists in the database
	const checkUserQuery = "SELECT * FROM users WHERE username = ? OR email = ?";
	connection.query(
		checkUserQuery,
		[usernameOrEmail, usernameOrEmail],
		(checkError, checkResults) => {
			if (checkError) {
				console.error("Error checking user:", checkError);
				const error = "An error occurred during the password reset process.";
				res.render("forgot-password", { error, success: null });
			} else if (checkResults.length === 0) {
				const error = "Username or email not found.";
				res.render("forgot-password", { error, success: null });
			} else {
				// Assuming the user exists, generate a reset token and send an email
				const user = checkResults[0];
				const resetToken = crypto.randomBytes(20).toString("hex");
				const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour

				// Update user with reset token and expiry
				const updateQuery =
					"UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?";
				connection.query(
					updateQuery,
					[resetToken, resetTokenExpiry, user.id],
					(updateError) => {
						if (updateError) {
							console.error("Error updating reset token:", updateError);
							const error =
								"An error occurred during the password reset process.";
							res.render("forgot-password", { error, success: null });
						} else {
							// Send email with reset link
							const resetLink = `http://localhost:3000/reset-password/${resetToken}`;
							const mailOptions = {
								to: user.email,
								subject: "Password Reset",
								text: `Click on the following link to reset your password: ${resetLink}`,
							};
							transporter.sendMail(mailOptions, (emailError, info) => {
								if (emailError) {
									console.error("Error sending email:", emailError);
									const error =
										"An error occurred during the password reset process.";
									res.render("forgot-password", { error, success: null });
								} else {
									console.log("Email sent: " + info.response);
									const success =
										"Password reset email sent successfully. Check your inbox.";
									res.render("forgot-password", { error: null, success });

									// Log the successful sending of the reset link in the database
									logPasswordResetActivity(
										user.username,
										"link sent successfully"
									);
								}
							});
						}
					}
				);
			}
		}
	);
});

// Function to log the password reset activity in the database
function logPasswordResetActivity(username, activity) {
	const logSql =
		"INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)";
	mysqlConnection.query(logSql, [username, activity], (error) => {
		if (error) {
			console.error("Error logging password reset activity:", error);
		} else {
			console.log("Password reset activity logged successfully");
		}
	});
}

// Handle Reset Password request
app.post("/reset-password/:token", async (req, res) => {
	const { token } = req.params;
	const { password, confirmPassword } = req.body;

	// Find user with matching reset token and not expired
	const selectQuery =
		"SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()";
	connection.query(selectQuery, [token], async (selectErr, selectResults) => {
		if (selectErr) {
			console.error("Error querying reset token:", selectErr);
			return res.status(500).json({ error: "Error querying reset token" });
		}

		if (selectResults.length === 0) {
			// Pass the error to the template when rendering the reset-password page
			return res.render("reset-password", {
				token,
				resetError: "Invalid or expired reset token",
			});
		}

		// Check if passwords match
		if (password !== confirmPassword) {
			// Pass the error to the template when rendering the reset-password page
			return res.render("reset-password", {
				token,
				resetError: "Passwords do not match",
			});
		}

		// Check if the new password meets complexity requirements
		if (!isStrongPassword(password)) {
			// Pass the error to the template when rendering the reset-password page
			return res.render("reset-password", {
				token,
				resetError:
					"Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.",
			});
		}

		// Hash the new password
		const hashedPassword = await bcrypt.hash(password, 10);

		// Update user's password and clear reset token
		const updateQuery =
			"UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE reset_token = ?";
		connection.query(updateQuery, [hashedPassword, token], (updateErr) => {
			if (updateErr) {
				console.error("Error updating password:", updateErr);
				// Pass the error to the template when rendering the reset-password page
				res.render("reset-password", {
					token,
					resetError: "Error updating password",
				});
			} else {
				// Redirect to the success page upon successful password reset
				res.redirect("/success");
			}
		});
	});
});

app.get("/success", (req, res) => {
	res.render("success");
});

app.get("/reset-password/:token", (req, res) => {
	const { token } = req.params;
	const error = req.query.error || null; // Get error from query parameter
	// Assuming you have this line in your server code where you render the reset-password view
	res.render("reset-password", {
		token,
		passwordValidationError: null,
		resetError: null,
		success: null,
	});
});
app.post("/reset-password", async (req, res) => {
	const { username, password, confirmPassword } = req.body;

	// Check if passwords match
	if (password !== confirmPassword) {
		return res.status(400).json({ error: "Passwords do not match" });
	}

	// Check if the new password meets complexity requirements
	if (!isStrongPassword(password)) {
		return res.status(400).json({
			error:
				"Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.",
		});
	}

	// Hash the new password
	const hashedPassword = await bcrypt.hash(password, 10);

	// Check if the user exists in the database before updating the password
	const userExists = await checkIfUserExists(username);

	if (!userExists) {
		return res.status(404).json({ error: "User does not exist" });
	}

	// Update user's password based on the username
	const updateQuery = "UPDATE users SET password = ? WHERE username = ?";
	connection.query(
		updateQuery,
		[hashedPassword, username],
		(updateErr, updateResults) => {
			if (updateErr) {
				console.error("Error updating password:", updateErr);
				return res.status(500).json({ error: "Error updating password" });
			}

			// Check if the update affected any rows
			if (updateResults.affectedRows > 0) {
				// Password update successful
				return res
					.status(200)
					.json({ success: "Password updated successfully" });
			} else {
				return res
					.status(404)
					.json({
						error: "User not found or password not updated.",
					});
			}
		}
	);
});
async function checkIfUserExists(username) {
	return new Promise((resolve, reject) => {
		const query = "SELECT * FROM users WHERE username = ?";
		connection.query(query, [username], (err, results) => {
			if (err) {
				reject(err);
			} else {
				resolve(results.length > 0);
			}
		});
	});
}

app.use(express.static("views"));

app.listen(PORT, () => {
	console.log(`Server is running on port ${PORT}`);
});
