const {
	Restaurants,
	RestaurantSaves,
	RestaurantReviews,
	RestaurantReviewImages,
	Members,
	sequelize,
	RestaurantImages,
	RestaurantHours,
} = require('../../models');
const { errorHandler } = require('../../util');
const { formatCase, formatImage, parseJsonSafe } = require('../../util/formatter');
const { deleteFile } = require('../../helpers');
const { optionalAuth } = require('../../util/optional-auth');
const { Op } = require('sequelize');

exports.getAllRestaurants = async (req, res, next) => {
	try {
		const { page, limit, search, sort, sortBy } = req.query;
		let whereClause = {};
		if (search) {
			whereClause = {
				[Op.or]: [{ name: { [Op.like]: `%${search}%` } }, { address: { [Op.like]: `%${search}%` } }],
			};
		}

		let attributes = [
			'id',
			'name',
			'address',
			'createdAt',
			'price',
			[
				sequelize.literal(`(
					SELECT AVG(rating)
					FROM restaurant_reviews
					WHERE restaurant_reviews.restaurant_id = Restaurants.id
				)`),
				'averageRating',
			],
		];

		const member = await optionalAuth(req);
		if (member?.id) {
			attributes.push([
				sequelize.literal(`(
					EXISTS (
						SELECT 1
						FROM restaurant_saves
						WHERE restaurant_saves.restaurant_id = Restaurants.id
						AND restaurant_saves.member_id = '${member.id}'
					)
				)`),
				'isSaved',
			]);
		}

		let order = [['createdAt', 'DESC']];

		const allowedSortFields = {
			price: 'price',
			createdAt: 'createdAt',
			averageRating: sequelize.literal(`(
				SELECT AVG(rating)
				FROM restaurant_reviews
				WHERE restaurant_reviews.restaurant_id = Restaurants.id
			)`),
		};
		const allowedSortBy = ['ASC', 'DESC'];

		if (sort && allowedSortFields[sort]) {
			const direction = allowedSortBy.includes((sortBy || '').toUpperCase()) ? sortBy.toUpperCase() : 'DESC';
			order = [[allowedSortFields[sort], direction]];
		}

		let restaurants, count;
		let pagination = undefined;

		if (page && limit) {
			const offset = (parseInt(page) - 1) * parseInt(limit);
			const result = await Restaurants.findAndCountAll({
				where: whereClause,
				attributes,
				include: [
					{
						model: RestaurantImages,
						as: 'images',
						attributes: ['image'],
					},
				],
				order,
				limit: parseInt(limit),
				offset: offset,
			});
			restaurants = result.rows;
			count = result.count;
			pagination = {
				currentPage: parseInt(page),
				totalPages: Math.ceil(count / parseInt(limit)),
				totalItems: count,
				itemsPerPage: parseInt(limit),
			};
		} else {
			restaurants = await Restaurants.findAll({
				where: whereClause,
				attributes,
				include: [
					{
						model: RestaurantImages,
						as: 'images',
						attributes: ['image'],
					},
				],
				order,
			});
		}

		restaurants = restaurants.map((item) => {
			const restaurant = item.get({ plain: true });
			if (!member?.id) {
				restaurant.isSaved = false;
			} else {
				restaurant.isSaved = !!restaurant.isSaved;
			}

			restaurant.images = restaurant.images?.map((img) => formatImage(img.image, 'restaurant-uploads'));
			return restaurant;
		});

		const response = {
			data: formatCase(restaurants),
		};
		if (pagination) {
			response.pagination = pagination;
		}

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

exports.getRestaurant = async (req, res, next) => {
	try {
		const { id } = req.params;

		let attributeIncludes = [
			[
				sequelize.literal(`(
							SELECT AVG(rating)
							FROM restaurant_reviews
							WHERE restaurant_reviews.restaurant_id = Restaurants.id
						)`),
				'averageRating',
			],
			[
				sequelize.literal(`(
							SELECT COUNT(*)
							FROM restaurant_reviews
							WHERE restaurant_reviews.restaurant_id = Restaurants.id
						)`),
				'reviewCount',
			],
		];

		const member = await optionalAuth(req);

		if (member?.id) {
			attributeIncludes.push([
				sequelize.literal(`(
							SELECT COUNT(*)
							FROM restaurant_saves
							WHERE restaurant_saves.restaurant_id = Restaurants.id
							AND restaurant_saves.member_id = '${member.id}'
						)`),
				'isSaved',
			]);
		}

		let include = [
			{
				model: RestaurantReviews,
				as: 'reviews',
				attributes: ['id'],
				include: [
					{
						model: RestaurantReviewImages,
						as: 'images',
						attributes: ['image'],
					},
				],
				order: [['createdAt', 'DESC']],
			},
			{
				model: RestaurantImages,
				as: 'images',
				attributes: ['image'],
			},
			{
				model: RestaurantHours,
				as: 'hours',
				attributes: {
					exclude: ['id', 'restaurant_id'],
				},
			},
		];

		if (member?.id) {
			include.push({
				model: RestaurantReviews,
				as: 'review',
				where: { member_id: member.id },
				required: false,
				include: [
					{
						model: RestaurantReviewImages,
						as: 'images',
						attributes: ['image'],
					},
				],
			});
		}

		const restaurant = await Restaurants.findByPk(id, {
			attributes: {
				include: attributeIncludes,
			},
			include,
		});

		if (!restaurant) {
			return errorHandler('Restaurant not found', 404);
		}

		const data = restaurant.get({ plain: true });
		data.price = data.price ? parseFloat(data.price) : 0.0;
		data.average_rating = data.averageRating ? parseFloat(data.averageRating) : 0.0;
		data.images = data.images?.map((img) => formatImage(img.image, 'restaurant-uploads'));
		data.features = parseJsonSafe(data.features) || [];
		data.features =
			data?.features?.length > 0
				? data.features.map((item) => ({ ...item, icon: formatImage(item.icon, 'icons') }))
				: [];

		data.isClosedNow = true;
		if (data.hours && data.hours.length > 0) {
			const now = new Date();
			const currentDay = now.getDay();
			const currentMinutes = now.getHours() * 60 + now.getMinutes();

			const todayHours = data.hours.find((hour) => hour.day_of_week === currentDay);

			if (todayHours && !todayHours.is_closed && todayHours.open_time && todayHours.close_time) {
				const [openHour, openMin] = todayHours.open_time.split(':').map(Number);
				const [closeHour, closeMin] = todayHours.close_time.split(':').map(Number);

				const openMinutes = openHour * 60 + openMin;
				const closeMinutes = closeHour * 60 + closeMin;

				data.isClosedNow = !(currentMinutes >= openMinutes && currentMinutes < closeMinutes);
			}
		}

		if (
			Array.isArray(data.reviews) &&
			data.reviews.some((review) => Array.isArray(review.images) && review.images.length > 0)
		) {
			data.reviewImages = data.reviews
				.map((review) => {
					return Array.isArray(review.images)
						? review.images?.map((img) => formatImage(img.image, 'restaurant-uploads'))
						: [];
				})
				.flat();
		}

		data.myReview = member?.id ? data.review : null;
		delete data.review;
		delete data.reviews;

		if (!member?.id) {
			data.isSaved = false;
		} else {
			data.isSaved = !!data.isSaved;
		}

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

exports.restaurantSave = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { restaurantId, save } = req.body;
		const { memberId } = req;

		const restaurant = await Restaurants.findByPk(restaurantId);
		if (!restaurant) {
			return errorHandler('Restaurant not found', 404);
		}

		const existingSave = await RestaurantSaves.findOne({
			where: { restaurant_id: restaurantId, member_id: memberId },
		});

		if (save === false) {
			if (existingSave) {
				await existingSave.destroy({ transaction });
				await transaction.commit();
				return res.status(200).json({
					success: true,
					message: 'Restaurant unsaved successfully',
				});
			} else {
				await transaction.commit();
				return res.status(200).json({
					success: true,
					message: 'Restaurant was not saved',
				});
			}
		} else {
			if (existingSave) {
				await transaction.commit();
				return res.status(200).json({
					success: true,
					message: 'Restaurant already saved',
				});
			}
			await RestaurantSaves.create(
				{
					restaurant_id: restaurantId,
					member_id: memberId,
				},
				{ transaction },
			);

			await transaction.commit();

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

exports.getSavedRestaurants = async (req, res, next) => {
	try {
		const { page, limit } = req.query;
		const { memberId } = req;

		let saves,
			count,
			pagination = undefined;

		const includeOptions = [
			{
				model: Restaurants,
				as: 'restaurant',
				attributes: [
					'id',
					'name',
					'address',
					'createdAt',
					'price',
					[
						sequelize.literal(`(
							SELECT AVG(rating)
							FROM restaurant_reviews
							WHERE restaurant_reviews.restaurant_id = restaurant.id
						)`),
						'averageRating',
					],
				],
				include: [
					{
						model: RestaurantImages,
						as: 'images',
						attributes: ['image'],
					},
				],
			},
		];

		if (page && limit) {
			const offset = (parseInt(page) - 1) * parseInt(limit);
			const result = await RestaurantSaves.findAndCountAll({
				where: { member_id: memberId },
				include: includeOptions,
				order: [['createdAt', 'DESC']],
				limit: parseInt(limit),
				offset: offset,
			});
			saves = result.rows;
			count = result.count;
			pagination = {
				currentPage: parseInt(page),
				totalPages: Math.ceil(count / parseInt(limit)),
				totalItems: count,
				itemsPerPage: parseInt(limit),
			};
		} else {
			saves = await RestaurantSaves.findAll({
				where: { member_id: memberId },
				include: includeOptions,
				order: [['createdAt', 'DESC']],
			});
		}

		const savedRestaurants = saves
			.map((save) => {
				const restaurant = save.restaurant
					? save.restaurant.get
						? save.restaurant.get({ plain: true })
						: save.restaurant
					: null;
				if (restaurant) {
					restaurant.images = restaurant.images?.map((img) => formatImage(img.image, 'restaurant-uploads'));
				}
				return restaurant;
			})
			.filter(Boolean);

		const response = {
			data: formatCase(savedRestaurants),
		};
		if (pagination) {
			response.pagination = pagination;
		}

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

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

		let whereClause = { restaurant_id: id };

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

		if (rating) {
			whereClause.rating = rating;
		}

		const includeOptions = [
			{
				model: Members,
				as: 'member',
				attributes: ['id', 'first_name', 'last_name', 'profile_image'],
			},
			{
				model: RestaurantReviewImages,
				as: 'images',
				attributes: ['image'],
				required: withImagesOnly === 'true' || withImagesOnly === true,
			},
		];

		const allowedSortFields = {
			createdAt: 'createdAt',
			rating: 'rating',
		};
		const allowedSortBy = ['ASC', 'DESC'];

		let order = [['createdAt', 'DESC']];
		if (sort && allowedSortFields[sort]) {
			const direction = allowedSortBy.includes((sortBy || '').toUpperCase()) ? sortBy.toUpperCase() : 'DESC';
			order = [[allowedSortFields[sort], direction]];
		}

		let reviews,
			count,
			pagination = undefined;

		const baseQuery = {
			attributes: { exclude: ['restaurant_id', 'member_id'] },
			where: whereClause,
			include: includeOptions,
			order,
		};

		if (page && limit) {
			const offset = (parseInt(page) - 1) * parseInt(limit);
			const result = await RestaurantReviews.findAndCountAll({
				...baseQuery,
				limit: parseInt(limit),
				offset,
			});
			reviews = result.rows;
			count = result.count;
			pagination = {
				currentPage: parseInt(page),
				totalPages: Math.ceil(count / parseInt(limit)),
				totalItems: count,
				itemsPerPage: parseInt(limit),
			};
		} else {
			reviews = await RestaurantReviews.findAll(baseQuery);
		}

		const formattedReviews = reviews.map((review) => {
			const data = review.get({ plain: true });

			if (data.is_anonymous) {
				data.member = {
					profile_image: process.env.ANONYMOUS_AVATAR,
					id: null,
					first_name: 'Anonymous',
					last_name: 'User',
				};
			} else {
				if (data.member) {
					data.member.profile_image = formatImage(data.member.profile_image, 'profile-uploads');
				}
				if (data.images) {
					data.images = data.images.map((img) => formatImage(img.image, 'restaurant-uploads'));
				}
			}
			return data;
		});

		const response = {
			data: formatCase(formattedReviews),
		};
		if (pagination) {
			response.pagination = pagination;
		}

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

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

		const restaurant = await Restaurants.findByPk(restaurantId);
		if (!restaurant) {
			return errorHandler('Restaurant not found', 404);
		}

		const exists = await RestaurantReviews.findOne({
			where: {
				member_id: memberId,
				restaurant_id: restaurantId,
			},
		});

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

		const newReview = await RestaurantReviews.create(
			{
				restaurant_id: restaurantId,
				member_id: memberId,
				rating: parseInt(rating),
				review,
				is_anonymous: !!isAnonymous,
			},
			{ transaction },
		);

		const reviewImages = req?.files?.restaurantReviewImgs || [];
		let images = [];

		if (reviewImages?.length > 0) {
			for (const img of reviewImages) {
				const image = await RestaurantReviewImages.create(
					{
						restaurant_review_id: newReview.id,
						image: img.filename,
					},
					{ transaction },
				);
				images.push(image);
			}
		}

		await transaction.commit();

		const createdReview = await RestaurantReviews.findByPk(newReview.id, {
			include: [
				{
					model: Members,
					as: 'member',
					attributes: ['id', 'first_name', 'last_name'],
				},
				{
					model: RestaurantReviewImages,
					as: 'images',
					attributes: ['id', 'image'],
				},
			],
		});

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

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

		const existingReview = await RestaurantReviews.findOne({
			where: { id: reviewId, member_id: memberId },
			include: [
				{
					model: RestaurantReviewImages,
					as: 'images',
				},
			],
		});

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

		const reviewImages = req?.files?.restaurantReviewImgs || [];
		let images = [];

		if (reviewImages?.length > 0 && existingReview.images && existingReview.images.length > 0) {
			for (const image of existingReview.images) {
				await deleteFile(image.image, 'restaurant-uploads');
				await RestaurantReviewImages.destroy({
					where: { id: image.id },
					transaction,
				});
			}
		}

		const updateData = {};
		if (rating !== undefined) {
			if (rating < 1 || rating > 5) {
				return errorHandler('Rating must be between 1 and 5', 400);
			}
			updateData.rating = parseInt(rating);
		}
		if (review !== undefined) updateData.review = review;
		if (isAnonymous !== undefined) updateData.is_anonymous = !!isAnonymous;

		await existingReview.update(updateData, { transaction });

		if (reviewImages?.length > 0) {
			for (const img of reviewImages) {
				const image = await RestaurantReviewImages.create(
					{
						restaurant_review_id: existingReview.id,
						image: img.filename,
					},
					{ transaction },
				);
				images.push(image);
			}
		}

		await transaction.commit();

		// Get the updated review with relations
		const updatedReview = await RestaurantReviews.findByPk(existingReview.id, {
			include: [
				{
					model: Members,
					as: 'member',
					attributes: ['id', 'first_name', 'last_name'],
				},
				{
					model: RestaurantReviewImages,
					as: 'images',
					attributes: ['id', 'image'],
				},
			],
		});

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

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

		const review = await RestaurantReviews.findOne({
			where: { id: reviewId, member_id: memberId },
			include: [
				{
					model: RestaurantReviewImages,
					as: 'images',
				},
			],
		});

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

		if (review.images && review.images.length > 0) {
			for (const image of review.images) {
				await deleteFile(image.image, 'restaurant-uploads');
				await RestaurantReviewImages.destroy({
					where: { id: image.id },
					transaction,
				});
			}
		}

		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);
	}
};
