const { deleteFile } = require('../../helpers');
const { HotelRooms, HotelReviews, HotelReviewImages, Members, sequelize, HotelBookings } = require('../../models');
const { formatImage, formatCase, parseJsonSafe } = require('../../util/formatter');
const { errorHandler } = require('../../util');
const { deleteFiles } = require('../files/files');
const { deleteFilesFromFileServer, moveFilesFromFileServer } = require('../../util/file-handlers');

exports.createHotelReview = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { bookingId, rating, review, isAnonymous } = req.body;

		const { memberId } = req;

		const booking = await HotelBookings.findOne({ where: { id: bookingId, member_id: memberId, status: 'Completed' } });
		if (!booking) {
			return errorHandler('No valid booking not found', 404);
		}

		const existingReview = await HotelReviews.findOne({
			where: { booking_id: bookingId, member_id: memberId },
		});

		if (existingReview) {
			return errorHandler('You have already reviewed this hotel', 400);
		}

		let record = await HotelReviews.create(
			{
				hotel_id: booking.hotel_id,
				booking_id: bookingId,
				hotel_room_id: booking.hotel_room_id,
				member_id: memberId,
				rating: parseInt(rating),
				is_anonymous: isAnonymous,
				review,
			},
			{ transaction },
		);

		let hotelReviewImgs = [];
		if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
			const reviewImgs = parseJsonSafe(req?.body?.hotelReviewImgs);
			if (reviewImgs && reviewImgs.length > 0) {
				const imagesToCreate = reviewImgs.map((img) => ({
					hotel_review_id: record.id,
					image: img,
				}));
				hotelReviewImgs = await HotelReviewImages.bulkCreate(imagesToCreate, { transaction });

				await moveFilesFromFileServer(reviewImgs, 'hotel-uploads');
			}
		} else {
			const reviewImgs = req?.files?.hotelReviewImgs;
			if (reviewImgs?.length > 0) {
				const imagesToCreate = reviewImgs.map((img) => ({
					hotel_review_id: record.id,
					image: img.filename,
				}));
				hotelReviewImgs = await HotelReviewImages.bulkCreate(imagesToCreate, { transaction });
			}
		}

		await transaction.commit();

		record = record.get({ plain: true });
		record.images = hotelReviewImgs.map((img) => ({
			id: img.id,
			image: formatImage(img.image, 'hotel-uploads'),
		}));

		return res.status(201).json({
			success: true,
			message: 'Review created successfully',
			data: record,
		});
	} catch (error) {
		if (transaction) await transaction.rollback();
		next(error);
	}
};

exports.getHotelReviews = async (req, res, next) => {
	try {
		const { id } = req.params;
		const { page, limit, rating } = req.query;

		let whereClause = { hotel_id: id };
		if (rating) {
			whereClause.rating = parseInt(rating);
		}

		const findOptions = {
			where: whereClause,
			include: [
				{
					model: HotelReviewImages,
					as: 'images',
					attributes: ['id', 'image'],
				},
				{
					model: Members,
					as: 'member',
					attributes: ['id', 'first_name', 'last_name', 'profile_image'],
				},
				{
					model: HotelRooms,
					as: 'room',
					attributes: ['id', 'name'],
					required: false,
				},
			],
			order: [['createdAt', 'DESC']],
		};

		let pagination = null;
		if (page && limit) {
			const offset = (parseInt(page) - 1) * parseInt(limit);
			findOptions.limit = parseInt(limit);
			findOptions.offset = offset;
		}

		const { count, rows: reviews } = await HotelReviews.findAndCountAll(findOptions);

		const formattedReviews = reviews.map((review) => {
			const reviewData = review.get({ plain: true });
			reviewData.images = reviewData.images?.map((img) => formatImage(img.image, 'hotel-uploads')) || [];

			if (reviewData.is_anonymous) {
				reviewData.member = {
					profile_image: process.env.ANONYMOUS_AVATAR,
					id: null,
					first_name: 'Anonymous',
					last_name: 'User',
				};
			} else {
				reviewData.member.profile_image = formatImage(reviewData.member.profile_image);
			}

			return reviewData;
		});

		if (page && limit) {
			pagination = {
				currentPage: parseInt(page),
				totalPages: Math.ceil(count / parseInt(limit)),
				totalItems: count,
				itemsPerPage: parseInt(limit),
			};
			return res.status(200).json({
				data: formatCase(formattedReviews),
				pagination,
			});
		} else {
			return res.status(200).json({
				data: formatCase(formattedReviews),
			});
		}
	} catch (error) {
		next(error);
	}
};

exports.getRoomReviews = async (req, res, next) => {
	try {
		const { id } = req.params;
		const { page, limit, rating } = req.query;

		const room = await HotelRooms.findByPk(id);
		if (!room) {
			return errorHandler('Hotel room not found', 404);
		}

		let whereClause = { hotel_room_id: id };
		if (rating) {
			whereClause.rating = parseInt(rating);
		}

		const findOptions = {
			where: whereClause,
			include: [
				{
					model: HotelReviewImages,
					as: 'images',
					attributes: ['id', 'image'],
				},
				{
					model: Members,
					as: 'member',
					attributes: ['id', 'first_name', 'last_name', 'profile_image'],
				},
			],
			order: [['createdAt', 'DESC']],
		};

		let pagination = null;
		if (page && limit) {
			const offset = (parseInt(page) - 1) * parseInt(limit);
			findOptions.limit = parseInt(limit);
			findOptions.offset = offset;
		}

		const { count, rows: reviews } = await HotelReviews.findAndCountAll(findOptions);

		const formattedReviews = reviews.map((review) => {
			const reviewData = review.get({ plain: true });
			reviewData.images = reviewData.images?.map((img) => formatImage(img.image, 'hotel-uploads')) || [];

			if (reviewData.is_anonymous) {
				reviewData.member = {
					profile_image: process.env.ANONYMOUS_AVATAR,
					id: null,
					first_name: 'Anonymous',
					last_name: 'User',
				};
			} else {
				reviewData.member.profile_image = formatImage(reviewData.member.profile_image);
			}

			return reviewData;
		});

		if (page && limit) {
			pagination = {
				currentPage: parseInt(page),
				totalPages: Math.ceil(count / parseInt(limit)),
				totalItems: count,
				itemsPerPage: parseInt(limit),
			};
			return res.status(200).json({
				data: formatCase(formattedReviews),
				pagination,
			});
		} else {
			return res.status(200).json({
				data: formatCase(formattedReviews),
			});
		}
	} catch (error) {
		next(error);
	}
};

exports.updateHotelReview = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { reviewId, rating, review, isAnonymous } = req.body;
		const { memberId } = req;

		const record = await HotelReviews.findByPk(reviewId);
		if (!record) return errorHandler('Review not found', 404);
		if (record.member_id !== memberId) return errorHandler('You can only update your own reviews', 403);
		if (rating && (rating < 1 || rating > 5)) return errorHandler('Rating must be between 1 and 5', 400);

		await record.update(
			{
				rating: rating ? parseInt(rating) : record.rating,
				review: review !== undefined ? review : record.review,
				is_anonymous: isAnonymous !== undefined ? isAnonymous : record.is_anonymous,
			},
			{ transaction },
		);

		let reviewImgs =
			process.env.USE_EXTERNAL_FILE_SERVER === 'true'
				? parseJsonSafe(req?.body?.hotelReviewImgs)
				: req?.files?.hotelReviewImgs;

		if (reviewImgs) {
			const oldImages = await HotelReviewImages.findAll({ where: { hotel_review_id: record.id } });
			const oldFilenames = oldImages.map((img) => img.image);
			let newFilenames = [];
			if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
				newFilenames = reviewImgs;
			} else if (Array.isArray(reviewImgs)) {
				newFilenames = reviewImgs.map((img) => img.filename);
			}

			await Promise.all(oldImages.map((img) => img.destroy({ transaction })));

			if (oldFilenames.length > 0) {
				if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
					await deleteFilesFromFileServer(oldFilenames, 'hotel-uploads');
				} else {
					await deleteFiles(oldFilenames, 'image', 'hotel-uploads');
				}
			}

			const imagesToCreate =
				process.env.USE_EXTERNAL_FILE_SERVER === 'true'
					? newFilenames.map((img) => ({ hotel_review_id: record.id, image: img }))
					: reviewImgs.map((img) => ({ hotel_review_id: record.id, image: img.filename }));

			await HotelReviewImages.bulkCreate(imagesToCreate, { transaction });

			if (process.env.USE_EXTERNAL_FILE_SERVER === 'true' && newFilenames.length > 0) {
				await moveFilesFromFileServer(newFilenames, 'hotel-uploads');
			}
		}

		await transaction.commit();
		return res.status(200).json({
			success: true,
			message: 'Review updated successfully',
			data: record,
		});
	} catch (error) {
		if (transaction) await transaction.rollback();
		next(error);
	}
};

exports.deleteHotelReview = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { id } = req.params;
		const { memberId } = req;

		const review = await HotelReviews.findByPk(id, {
			include: [
				{
					model: HotelReviewImages,
					as: 'images',
				},
			],
		});

		if (!review) {
			return errorHandler('Review not found', 404);
		}

		if (review.member_id !== memberId) {
			return errorHandler('You can only delete your own reviews', 403);
		}

		const imagesToDelete = review.images?.map((img) => img.image) || [];
		if (imagesToDelete.length > 0) {
			await Promise.all((review.images || []).map((img) => img.destroy({ transaction })));
			if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
				await deleteFilesFromFileServer(imagesToDelete, 'hotel-uploads');
			} else {
				await deleteFiles(imagesToDelete, 'image', 'hotel-uploads');
			}
		}

		await review.destroy({ transaction });

		await transaction.commit();

		return res.status(200).json({
			success: true,
			message: 'Review deleted successfully',
		});
	} catch (error) {
		if (transaction) await transaction.rollback();
		next(error);
	}
};
