const { Op } = require('sequelize');
const { deleteFile } = require('../../helpers');
const {
	Activities,
	ActivityImages,
	ActivityLikes,
	ActivityReviews,
	ActivityReviewImages,
	ActivityItineraries,
	sequelize,
	Members,
} = require('../../models');
const { formatImage, formatCase, calculateAverageRating, parseJsonSafe } = require('../../util/formatter');
const { optionalAuth } = require('../../util/optional-auth');
const { deleteFilesFromFileServer, deleteFiles, moveFilesFromFileServer } = require('../../util/file-handlers');

exports.getAllActivities = async (req, res, next) => {
	try {
		let { page, limit } = req.query;
		let options = {
			include: [
				{ model: ActivityImages, as: 'images', attributes: ['image'] },
				{ model: ActivityItineraries, as: 'itineraries', attributes: { exclude: ['id', 'activity_id'] } },
			],
			order: [['createdAt', 'DESC']],
		};

		const member = await optionalAuth(req);

		if (member?.id) {
			options.attributes = {
				include: [
					[
						sequelize.literal(`EXISTS(
							SELECT 1
							FROM activity_likes AS l
							WHERE l.activity_id = Activities.id AND l.member_id = "${member.id}"
						)`),
						'is_liked',
					],
				],
			};
		}

		if (page && limit) {
			page = parseInt(page, 10);
			limit = parseInt(limit, 10);
			options.offset = (page - 1) * limit;
			options.limit = limit;
		}

		let result;
		if (page && limit) {
			result = await Activities.findAndCountAll(options);
		} else {
			result = { rows: await Activities.findAll(options) };
		}

		let activities = result.rows.map((item) => {
			let activity = item.get({ plain: true });
			activity.about = activity.about ? parseJsonSafe(activity.about) : null;
			activity.about = activity?.about.map((item) => {
				item.icon = item.icon ? formatImage(item.icon, 'icons') : null;

				return item;
			});
			activity.itineraries =
				activity?.itineraries.length > 0
					? activity.itineraries.map((item) => {
							item.icon = item.icon ? formatImage(item.icon, 'icons') : null;
							return item;
						})
					: [];

			if (!member?.id) {
				activity.isLiked = false;
			}

			activity.images = activity.images.map((img) => formatImage(img.image));
			return activity;
		});

		const response = { data: formatCase(activities) };
		if (page && limit) {
			response.pagination = {
				total: result.count,
				page: Number(page),
				limit: Number(limit),
				pages: Math.ceil(result.count / limit),
			};
		}

		return res.status(200).json(response);
	} catch (error) {
		next(error);
	}
};

exports.getActivity = async (req, res, next) => {
	try {
		let attributeIncludes = [
			[
				sequelize.literal(`(
							SELECT COUNT(*)
							FROM activity_reviews AS r
							WHERE r.activity_id = Activities.id
						)`),
				'review_count',
			],
			[
				sequelize.literal(`TRUNCATE((
			SELECT AVG(r.guide_rating)
			FROM activity_reviews AS r
			WHERE r.activity_id = Activities.id
				 ), 1)`),
				'guide_review_average',
			],
			[
				sequelize.literal(`TRUNCATE((
			SELECT AVG(r.transportation_rating)
			FROM activity_reviews AS r
			WHERE r.activity_id = Activities.id
			 ), 1)`),
				'transportation_review_average',
			],
			[
				sequelize.literal(`TRUNCATE((
			SELECT AVG(r.value_for_money_rating)
			FROM activity_reviews AS r
			WHERE r.activity_id = Activities.id
				), 1)`),
				'value_for_money_review_average',
			],
		];

		const member = await optionalAuth(req);

		if (member?.id) {
			attributeIncludes.push([
				sequelize.literal(`EXISTS(
							SELECT 1
							FROM activity_likes AS l
							WHERE l.activity_id = Activities.id AND l.member_id = "${member.id}"
						)`),
				'is_liked',
			]);
		}

		let activityInclude = [
			{ model: ActivityImages, as: 'images', attributes: ['image'] },
			{
				model: ActivityReviews,
				as: 'reviews',
				attributes: ['guide_rating', 'transportation_rating', 'value_for_money_rating'],
				include: [{ model: ActivityReviewImages, as: 'images', attributes: ['image'] }],
			},
			{
				model: ActivityReviews,
				as: 'highlightedReviews',
				where: {
					is_highlighted: true,
				},
				required: false,
				attributes: {
					exclude: ['activity_id', 'member_id', 'is_highlighted'],
				},
				include: [
					{ model: ActivityReviewImages, as: 'images', attributes: ['image'] },
					{
						model: Members,
						as: 'member',
						attributes: ['id', 'first_name', 'last_name', 'profile_image'],
					},
				],
			},
			{ model: ActivityItineraries, as: 'itineraries', attributes: { exclude: ['id', 'activity_id'] } },
		];

		if (member?.id) {
			activityInclude.push({
				where: { member_id: member.id },
				model: ActivityReviews,
				as: 'review',
				attributes: ['id', 'guide_rating', 'transportation_rating', 'value_for_money_rating', 'review', 'is_anonymous'],
				include: [
					{
						model: ActivityReviewImages,
						as: 'images',
						attributes: ['image'],
					},
				],
				required: false,
			});
		}

		let activity = await Activities.findByPk(req.params.id, {
			attributes: {
				include: attributeIncludes,
			},
			include: activityInclude,
		});

		if (!activity) return res.status(404).json({ success: false, message: 'Activity not found' });

		activity = activity.get({ plain: true });
		activity.about = activity.about ? parseJsonSafe(activity.about) : null;
		activity.about = activity?.about.map((item) => {
			item.icon = item.icon ? formatImage(item.icon, 'icons') : null;
			return item;
		});

		activity.review_count = activity.reviews.length;
		activity.over_all_review_average = calculateAverageRating(activity);
		activity.guide_review_average = Number(activity.guide_review_average);
		activity.transportation_review_average = Number(activity.transportation_review_average);
		activity.value_for_money_review_average = Number(activity.value_for_money_review_average);

		activity.highlightedReviews = activity.highlightedReviews.map((review) => {
			review.images = review.images.map((img) => formatImage(img.image));

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

			return review;
		});

		activity.images = activity.images.map((img) => formatImage(img.image));
		activity.reviewImages = activity.reviews
			.map((review) => {
				return review.images.map((img) => formatImage(img.image));
			})
			.flat();

		if (activity?.itineraries?.length > 0) {
			activity.itineraries.sort((a, b) => a.order - b.order);
			activity.itineraries = activity.itineraries.map((item) => {
				item.icon = item.icon ? formatImage(item.icon, 'icons') : null;
				return item;
			});
		}

		if (!member?.id) {
			activity.myReview = null;
		} else {
			activity.myReview = activity.review;
			if (activity.myReview) {
				activity.myReview.images = activity?.myReview?.images?.map((img) => formatImage(img.image));
			}
		}
		delete activity.review;
		delete activity.reviews;

		let mightAlsoLikeAttributes = [
			'id',
			'type',
			'title',
			'duration',
			'pickup_available',
			'old_price',
			'price',
			'price_type',
			[
				sequelize.literal(`TRUNCATE((
					SELECT AVG(
						(r.guide_rating + r.transportation_rating + r.value_for_money_rating) / 3
					)
					FROM activity_reviews AS r
					WHERE r.activity_id = Activities.id
				), 1)`),
				'average_rating',
			],
			[
				sequelize.literal(`(
					SELECT COUNT(*)
					FROM activity_reviews AS r
					WHERE r.activity_id = Activities.id
				)`),
				'review_count',
			],
		];

		if (member?.id) {
			mightAlsoLikeAttributes.push([
				sequelize.literal(`EXISTS(
						SELECT 1
						FROM activity_likes AS l
						WHERE l.activity_id = Activities.id AND l.member_id = "${member.id}"
					)`),
				'is_liked',
			]);
		}

		let mightAlsoLike = await Activities.findAll({
			where: {
				id: { [Op.ne]: activity.id },
			},
			attributes: mightAlsoLikeAttributes,
			include: [
				{ model: ActivityImages, as: 'images', attributes: ['image'] },
				{ model: ActivityItineraries, as: 'itineraries', attributes: { exclude: ['id', 'activity_id'] } },
			],
			order: sequelize.random(),
			limit: 8,
		});

		mightAlsoLike =
			mightAlsoLike?.length > 0
				? mightAlsoLike.map((item) => {
						let act = item.get({ plain: true });
						act.images = act.images.map((img) => formatImage(img.image));
						act.is_liked = !!act?.is_liked;
						if (act.itineraries.length > 0) {
							act.itineraries.sort((a, b) => a.order - b.order);
							activity.itineraries = activity.itineraries.map((item) => {
								item.icon = item.icon ? formatImage(item.icon, 'icons') : null;
								return item;
							});
						}
						return act;
					})
				: [];

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

exports.likeActivity = async (req, res, next) => {
	try {
		const { activityId, like } = req.body;
		const { memberId } = req;

		const activity = await Activities.findByPk(activityId);
		if (!activity) {
			return res.status(404).json({ success: false, message: 'Activity not found.' });
		}

		const existingLike = await ActivityLikes.findOne({ where: { activity_id: activityId, member_id: memberId } });

		if (like) {
			if (existingLike) {
				return res.status(200).json({ success: true, message: 'You have already liked this activity.' });
			}
			const newLike = await ActivityLikes.create({ activity_id: activityId, member_id: memberId });
			return res.status(201).json({ success: true, data: newLike, message: 'Activity liked successfully.' });
		} else {
			if (!existingLike) {
				return res.status(200).json({ success: true, message: 'You have not liked this activity yet.' });
			}
			await existingLike.destroy();
			return res.status(200).json({ success: true, message: 'Activity unliked successfully.' });
		}
	} catch (error) {
		next(error);
	}
};

exports.getActivityReviews = async (req, res, next) => {
	try {
		const { id } = req.params;
		let { page, limit, search, sort, sortBy } = req.query;

		let options = {
			where: { activity_id: id },
			include: [
				{ model: ActivityReviewImages, as: 'images', attributes: ['image'] },
				{
					model: Members,
					as: 'member',
					attributes: ['id', 'first_name', 'last_name', 'profile_image'],
				},
			],
			order: [
				[
					sort && ['createdAt', 'guide_rating', 'transportation_rating', 'value_for_money_rating'].includes(sort)
						? sort
						: 'createdAt',
					sortBy && ['ASC', 'DESC'].includes(sortBy.toUpperCase()) ? sortBy.toUpperCase() : 'DESC',
				],
			],
		};

		if (search) {
			options.where.review = { [sequelize.Op.like]: `%${search}%` };
		}

		let result;
		if (page && limit) {
			page = parseInt(page, 10);
			limit = parseInt(limit, 10);
			options.offset = (page - 1) * limit;
			options.limit = limit;
			result = await ActivityReviews.findAndCountAll(options);
		} else {
			result = { rows: await ActivityReviews.findAll(options) };
		}

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

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

			return r;
		});

		const response = { data: formatCase(reviews) };
		if (page && limit) {
			response.pagination = {
				total: result.count,
				page: Number(page),
				limit: Number(limit),
				pages: Math.ceil(result.count / limit),
			};
		}

		return res.status(200).json(response);
	} catch (error) {
		next(error);
	}
};

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

		const exists = await ActivityReviews.findOne({
			where: { activity_id: activityId, member_id: memberId },
		});

		if (exists) {
			return res.status(409).json({ success: false, message: 'You have already reviewed this activity.' });
		}

		const record = await ActivityReviews.create({
			activity_id: activityId,
			member_id: memberId,
			...formatCase(req.body, 'snake'),
		});

		let reviewImages;
		if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
			const reviewImgs = parseJsonSafe(req.body.reviewImgs);
			if (reviewImgs?.length > 0) {
				reviewImages = await ActivityReviewImages.bulkCreate(
					reviewImgs.map((image) => ({
						activity_review_id: record.id,
						image,
					})),
				);
				await moveFilesFromFileServer({ filenames: reviewImgs, destination: 'activity-uploads' });
			}
		} else {
			reviewImages = req?.files?.reviewImgs;
			if (reviewImages?.length > 0) {
				reviewImages = await ActivityReviewImages.bulkCreate(
					reviewImages.map((image) => ({
						activity_review_id: record.id,
						image: image.filename,
					})),
				);
			}
		}

		return res.status(201).json({ success: true, data: { ...record.get({ plain: true }), images: reviewImages } });
	} catch (error) {
		next(error);
	}
};

exports.updateReview = async (req, res, next) => {
	try {
		const { reviewId, ...updates } = req.body;
		const { memberId } = req;

		const reviewRecord = await ActivityReviews.findOne({ where: { id: reviewId, member_id: memberId } });
		if (!reviewRecord) {
			return res.status(404).json({ success: false, message: 'Review not found or not owned by member.' });
		}

		await reviewRecord.update(formatCase(updates, 'snake'));

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

		if (newImages?.length > 0) {
			const oldImages = await ActivityReviewImages.findAll({ where: { activity_review_id: reviewId } });

			await ActivityReviewImages.destroy({ where: { activity_review_id: reviewId } });

			if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
				await ActivityReviewImages.bulkCreate(
					newImages.map((image) => ({
						activity_review_id: reviewId,
						image: image,
					})),
				);

				await moveFilesFromFileServer({
					filenames: newImages,
					destination: 'activity-uploads',
				});
			} else {
				await ActivityReviewImages.bulkCreate(
					newImages.map((image) => ({
						activity_review_id: reviewId,
						image: image.filename,
					})),
				);
			}

			if (oldImages.length > 0) {
				const filenamesToDelete = oldImages.map((img) => img.image);
				if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
					await deleteFilesFromFileServer({
						filenames: filenamesToDelete,
						destination: 'activity-uploads',
					});
				} else {
					deleteFiles(filenamesToDelete, 'activity-uploads');
				}
			}
		}

		return res.status(200).json({
			success: true,
			data: { ...reviewRecord.get({ plain: true }) },
			message: 'Review updated successfully.',
		});
	} catch (error) {
		next(error);
	}
};

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

		const reviewRecord = await ActivityReviews.findOne({ where: { id: req.params.id, member_id: memberId } });
		if (!reviewRecord) {
			return res.status(404).json({ success: false, message: 'Review not found or not owned by member.' });
		}

		const reviewImages = await ActivityReviewImages.findAll({ where: { review_id: reviewRecord.id } });
		if (reviewImages.length > 0) {
			await Promise.all(reviewImages.map((img) => img.destroy()));
			const filenamesToDelete = reviewImages.map((img) => img.image);
			if (process.env.USE_EXTERNAL_FILE_SERVER === 'true') {
				await deleteFilesFromFileServer({
					filenames: filenamesToDelete,
					destination: 'activity-uploads',
				});
			} else {
				for (const img of filenamesToDelete) {
					deleteFile(img, 'activity-uploads');
				}
			}
		}

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