const { Members, PasswordResetTokens, RefreshTokens } = require('../../models');
const crypto = require('crypto');
const { errorHandler } = require('../../util');
const { formatCase, formatImage } = require('../../util/formatter');
const passport = require('../../util/passport/members/member-passport-config');
const jwt = require('jsonwebtoken');
const deleteFile = require('../../helpers/delete-file');
const { deleteUploadedFiles, deleteFilesFromFileServer, moveFilesFromFileServer } = require('../../util/file-handlers');
const { Op } = require('sequelize');
const { sendEmail } = require('../../services/email');
const { passwordReset } = require('../../templates/email');
const { generateToken, hashToken } = require('../../util/token');

exports.accountCreate = (req, res, next) => {
	passport.authenticate('signup-member', { session: false }, async (err, member, info) => {
		try {
			if (err) {
				return next(err);
			}

			if (!member) {
				return res.status(400).json({ success: false, message: info.message || 'Registration failed' });
			}

			// const token = jwt.sign({ memberId: member.id, userType: 'Member' }, JWT_SECRET, { expiresIn: '2h' });

			// return res.status(201).json({
			// 	success: true,
			// 	token,
			// });
			const token = generateToken({ memberId: member.id, userType: 'Member' }, '2h');
			const refreshToken = generateToken({ memberId: member.id, userType: 'Member' }, '30d', true);
			const hashedRefreshToken = hashToken(refreshToken);

			await RefreshTokens.create({
				user_id: member.id,
				user_type: 'Member',
				token: hashedRefreshToken,
				expiration: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
			});

			res.status(200).json({ token, refreshToken, member });
		} catch (error) {
			return next(error);
		}
	})(req, res, next);
};

exports.memberLogin = (req, res, next) => {
	passport.authenticate('login-member', async (err, member, info) => {
		if (err) {
			return next(err);
		}
		if (!member) {
			return res.status(400).json({ success: false, message: info.message });
		}

		const token = generateToken({ memberId: member.id, userType: 'Member' }, '2h');
		const refreshToken = generateToken({ memberId: member.id, userType: 'Member' }, '30d', true);
		const hashedRefreshToken = hashToken(refreshToken);

		await RefreshTokens.create({
			user_id: member.id,
			user_type: 'Member',
			token: hashedRefreshToken,
			expiration: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
		});

		const formatMember = member.get({ plain: true });
		delete formatMember.password;

		res.status(200).json({ token, refreshToken, member: formatCase(formatMember) });
	})(req, res, next);
};

exports.getProfile = (req, res, next) => {
	const { memberId } = req;

	Members.findOne({
		where: {
			id: memberId,
		},
		attributes: {
			exclude: ['password'],
		},
	})
		.then((member) => {
			if (!member) {
				return errorHandler('Member not found', 404);
			}

			let memberData = member.get({ plain: true });

			member.profile_image = member.profile_image ? formatImage(member.profile_image, 'profile-uploads') : null;
			member.cover_image = member.cover_image ? formatImage(member.cover_image, 'profile-uploads') : null;

			memberData = formatCase(memberData);

			return res.status(200).json({ success: true, data: memberData });
		})
		.catch((err) => {
			next(err);
		});
};

exports.updateProfile = async (req, res, next) => {
	try {
		const { memberId } = req;
		const body = formatCase(req.body, 'snake');
		let member = await Members.findByPk(memberId);

		if (body.email_address) {
			const exists = await Members.findOne({
				where: {
					email_address: body.email_address,
					id: { [Op.ne]: memberId },
				},
			});
			if (exists) return errorHandler('Email address already in use', 400);
		}

		if (!member) return errorHandler('Member not found', 404);

		delete body.password;
		delete body.verified;

		await member.update(body);

		const { profileImg, coverImg } = req.files || {};
		const oldProfileImg = member.profile_image;
		const oldCoverImg = member.cover_image;

		let newProfileImg = oldProfileImg;
		let newCoverImg = oldCoverImg;

		if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
			if (req.body.profileImg && req.body.profileImg !== oldProfileImg) {
				newProfileImg = req.body.profileImg;
				member.profile_image = newProfileImg;
			}
			if (req.body.coverImg && req.body.coverImg !== oldCoverImg) {
				newCoverImg = req.body.coverImg;
				member.cover_image = newCoverImg;
			}
		} else {
			if (profileImg?.[0] && profileImg[0].filename !== oldProfileImg) {
				newProfileImg = profileImg[0].filename;
				member.profile_image = newProfileImg;
			}
			if (coverImg?.[0] && coverImg[0].filename !== oldCoverImg) {
				newCoverImg = coverImg[0].filename;
				member.cover_image = newCoverImg;
			}
		}

		await member.save();

		if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
			const filesToDelete = [];
			const filesToMove = [];
			if (oldProfileImg && newProfileImg !== oldProfileImg) {
				filesToDelete.push(oldProfileImg);
				filesToMove.push(newProfileImg);
			}
			if (oldCoverImg && newCoverImg !== oldCoverImg) {
				filesToDelete.push(oldCoverImg);
				filesToMove.push(newCoverImg);
			}
			if (filesToDelete.length) {
				deleteFilesFromFileServer({ filenames: filesToDelete, destination: 'profile-uploads' });
			}
			if (filesToMove.length) {
				moveFilesFromFileServer({ filenames: filesToMove, destination: 'profile-uploads' });
			}
		} else {
			if (oldProfileImg && newProfileImg !== oldProfileImg) {
				deleteFile(oldProfileImg, 'profile-uploads');
			}
			if (oldCoverImg && newCoverImg !== oldCoverImg) {
				deleteFile(oldCoverImg, 'profile-uploads');
			}
		}

		const memberPlain = member.get({ plain: true });
		delete memberPlain.password;
		memberPlain.profile_image = memberPlain.profile_image
			? formatImage(memberPlain.profile_image, 'profile-uploads')
			: null;
		memberPlain.cover_image = memberPlain.cover_image ? formatImage(memberPlain.cover_image, 'profile-uploads') : null;

		return res.status(200).json({ success: true, data: formatCase(memberPlain) });
	} catch (error) {
		deleteUploadedFiles(req);
		next(error);
	}
};

exports.changePassword = async (req, res, next) => {
	try {
		const { oldPassword, newPassword } = req.body;
		const { memberId } = req;

		const member = await Members.findByPk(memberId);

		if (!(await member.validatePassword(oldPassword))) {
			return errorHandler('Invalid old password', 400);
		}

		member.password = newPassword;
		await member.save();

		return res.status(200).json({ success: true, message: 'Password changed successfully' });
	} catch (error) {
		next(error);
	}
};

exports.requestPasswordReset = async (req, res, next) => {
	try {
		const { email } = req.body;
		if (!email) return res.status(400).json({ success: false, message: 'Email is required' });
		const member = await Members.findOne({ where: { email_address: email } });
		if (!member) return res.status(404).json({ success: false, message: 'Member not found' });

		const existingToken = await PasswordResetTokens.findOne({
			where: {
				email: member.email_address,
			},
		});

		if (existingToken) {
			const now = new Date();
			if (existingToken.expires_at > now) {
				const timeRemaining = Math.ceil((existingToken.expires_at.getTime() - now.getTime()) / 1000);
				return res.status(400).json({
					success: false,
					message: `Please wait ${timeRemaining} seconds before requesting another reset.`,
					timeRemaining,
				});
			} else {
				await existingToken.destroy();
			}
		}

		const token = crypto.randomBytes(20).toString('hex');
		const expiresAt = new Date(Date.now() + 3 * 60 * 1000);
		const record = await PasswordResetTokens.create({
			email: member.email_address,
			token,
			expires_at: expiresAt,
		});

		if (process.env.USE_EXTERNAL_EMAIL_SERVER === 'true') {
			console.log('Using external email server for password reset');

			if (!process.env.EMAIL_SERVER_URL || !process.env.EMAIL_API_KEY) {
				throw new Error('Email server URL or API key is not defined in environment variables');
			}

			try {
				const response = await fetch(process.env.EMAIL_SERVER_URL, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
						'x-api-key': process.env.EMAIL_API_KEY,
					},
					body: JSON.stringify({
						type: 'password_reset',
						email: member.email_address,
						token: token,
					}),
				});

				console.log('Response from external email server:', response.status, response.statusText);

				if (!response.ok) {
					console.error('Failed to send email:', await response.text());
					await record.destroy();
					return res
						.status(500)
						.json({ success: false, message: 'Failed to send reset email. Please try again later.' });
				}
			} catch (error) {
				console.error('Error sending email:', error);
				await record.destroy();
				return res.status(500).json({ success: false, message: 'Failed to send reset email. Please try again later.' });
			}
		} else {
			console.log('Using internal email service for password reset');

			try {
				await sendEmail({
					to: member.email_address,
					subject: 'Password Reset Request',
					html: passwordReset(token, member.email_address),
				});
			} catch (error) {
				await record.destroy();
				return res.status(500).json({ success: false, message: 'Failed to send reset email. Please try again later.' });
			}
		}

		return res.status(201).json({ success: true, message: 'Reset token created.' });
	} catch (error) {
		next(error);
	}
};

exports.resetPassword = async (req, res, next) => {
	try {
		const { token, password } = req.body;
		if (!token || !password) {
			return res.status(400).json({ success: false, message: 'Token and new password are required' });
		}
		const resetToken = await PasswordResetTokens.findOne({ where: { token } });
		if (!resetToken || resetToken.expires_at < new Date()) {
			return res.status(400).json({ success: false, message: 'Token is invalid or expired', invalidToken: true });
		}

		const member = await Members.findOne({ where: { email_address: resetToken.email } });
		if (!member) {
			await resetToken.destroy();
			return res.status(404).json({ success: false, message: 'Member not found', invalidToken: true });
		}

		member.password = password;
		await member.save();
		await resetToken.destroy();
		return res.status(200).json({ success: true, message: 'Password reset successful' });
	} catch (error) {
		next(error);
	}
};

exports.allMembers = async (req, res, next) => {
	try {
		const { search = '', forTags = false, page = 1, limit = 25 } = req.query;
		const { memberId } = req;

		const where = {};
		if (search) {
			where[Op.or] = [{ first_name: { [Op.like]: `%${search}%` } }, { last_name: { [Op.like]: `%${search}%` } }];
		}
		if (!forTags && memberId) {
			where.id = { [Op.ne]: memberId };
		}

		const pageNum = parseInt(page, 10) > 0 ? parseInt(page, 10) : 1;
		const limitNum = parseInt(limit, 10) > 0 ? parseInt(limit, 10) : 25;
		const offset = (pageNum - 1) * limitNum;

		const { rows: members, count: total } = await Members.findAndCountAll({
			where,
			attributes: ['id', 'first_name', 'last_name', 'profile_image'],
			order: [['first_name', 'ASC']],
			limit: limitNum,
			offset,
		});

		const formattedMembers = members.map((member) => ({
			...member.get({ plain: true }),
			profile_image: member.profile_image ? formatImage(member.profile_image, 'profile-uploads') : null,
		}));

		return res.status(200).json({
			success: true,
			data: formatCase(formattedMembers),
			pagination: {
				page: pageNum,
				limit: limitNum,
				total,
				totalPages: Math.ceil(total / limitNum),
			},
		});
	} catch (error) {
		next(error);
	}
};

exports.deleteAccount = async (req, res, next) => {
	try {
		const { password } = req.body;
		const { memberId } = req;
		const member = await Members.findByPk(memberId);

		if (!member) {
			return errorHandler('Member not found', 404);
		}

		const isValid = await member.validatePassword(password);
		if (!isValid) {
			return res.status(400).json({ success: false, message: 'Invalid password' });
		}

		const profileImage = member.profile_image;
		const coverImage = member.cover_image;

		await member.destroy();

		if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
			const filesToDelete = [];
			if (profileImage) filesToDelete.push(profileImage);
			if (coverImage) filesToDelete.push(coverImage);

			if (filesToDelete.length) {
				deleteFilesFromFileServer({ filenames: filesToDelete, destination: 'profile-uploads' });
			}
		} else {
			if (profileImage) {
				deleteFile(profileImage, 'profile-uploads');
			}
			if (coverImage) {
				deleteFile(coverImage, 'profile-uploads');
			}
		}

		return res.status(200).json({ success: true, message: 'Account deleted successfully' });
	} catch (error) {
		next(error);
	}
};

exports.refreshToken = async (req, res, next) => {
	try {
		const { refreshToken } = req.body;

		if (!refreshToken) {
			return res.status(401).json({
				success: false,
				message: 'Refresh token is required',
			});
		}

		try {
			const secret = process.env.REFRESH_SECRET;
			if (!secret) {
				throw new Error('Refresh secret is not defined');
			}
			jwt.verify(refreshToken, secret);
		} catch (err) {
			await RefreshTokens.destroy({
				where: {
					token: hashToken(refreshToken),
				},
			});
			console.log(err);

			return res.status(401).json({
				message: 'Invalid token. Please log in again',
				invalid_refresh_token: true,
			});
		}

		const hashedRefreshToken = hashToken(refreshToken);

		const token = await RefreshTokens.findOne({
			where: {
				token: hashedRefreshToken,
				user_type: 'Member',
			},
		});

		if (!token) {
			return res.status(401).json({
				success: false,
				message: 'Invalid refresh token',
				invalid_refresh_token: true,
			});
		}

		const newToken = generateToken({ memberId: token.user_id, userType: 'Member' }, '2hrs');

		res.status(200).json({ token: newToken });

		await RefreshTokens.destroy({
			where: {
				expiration: { [Op.lt]: new Date() },
			},
		});
	} catch (error) {
		next(error);
	}
};

exports.checkPassword = async (req, res, next) => {
	try {
		const { password } = req.body;
		const { memberId } = req;

		const member = await Members.findByPk(memberId);
		if (!member) {
			return errorHandler('Member not found', 404);
		}

		const isValid = await member.validatePassword(password);
		if (!isValid) {
			return res.status(400).json({ success: false, message: 'Invalid password' });
		}

		return res.status(200).json({ success: true, message: 'Password is valid' });
	} catch (error) {
		next(error);
	}
};
