const { Op } = require('sequelize');
const {
	Hotels,
	HotelImages,
	HotelRooms,
	HotelRoomImages,
	HotelBookings,
	HotelReviews,
	HotelReviewImages,
	Members,
	sequelize,
} = require('../../models');
const { errorHandler } = require('../../util');
const { formatImage, formatCase, parseJsonSafe } = require('../../util/formatter');
const { calculateNights, getExistingBookings } = require('../../util/booking');
const { optionalAuth } = require('../../util/optional-auth');
const { generateReferenceNumber } = require('../../util/reference-no');

exports.getAllHotels = async (req, res, next) => {
	try {
		const { search, sort, sortBy } = req.query;

		const whereClause = {};
		if (search) {
			whereClause[Op.or] = [
				{ name: { [Op.like]: `%${search}%` } },
				{ description: { [Op.like]: `%${search}%` } },
				{ location: { [Op.like]: `%${search}%` } },
			];
		}

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

		const sortDirection = sortBy === 'ASC' || sortBy === 'DESC' ? sortBy : 'DESC';
		if (sort === 'price' || sort === 'minPrice') {
			order = [[sequelize.literal('minPrice'), sortDirection]];
		} else if (sort === 'rating') {
			order = [[sequelize.literal('averageRating'), sortDirection]];
		} else if (sort === 'createdAt') {
			order = [['createdAt', sortDirection]];
		}

		let hotels = await Hotels.findAll({
			where: whereClause,
			include: [
				{
					model: HotelImages,
					as: 'images',
					attributes: ['image'],
				},
			],
			attributes: [
				'id',
				'name',
				'address',
				'price',
				[
					sequelize.literal(`(
                            SELECT AVG(rating)
                            FROM hotel_reviews
                            WHERE hotel_reviews.hotel_id = Hotels.id
                        )`),
					'averageRating',
				],
				[
					sequelize.literal(`(
                            SELECT COUNT(*)
                            FROM hotel_reviews
                            WHERE hotel_reviews.hotel_id = Hotels.id
                        )`),
					'reviewCount',
				],
				[
					sequelize.literal(`(
                            SELECT MIN(price)
                            FROM hotel_rooms
                            WHERE hotel_rooms.hotel_id = Hotels.id
                        )`),
					'minPrice',
				],
				[
					sequelize.literal(`(
                            SELECT MAX(price)
                            FROM hotel_rooms
                            WHERE hotel_rooms.hotel_id = Hotels.id
                        )`),
					'maxPrice',
				],
			],
			order,
		});

		hotels = hotels.map((hotel) => {
			let item = hotel.get({ plain: true });

			if (item.images && item.images.length > 0) {
				item.images = item.images.map((img) => formatImage(img.image, 'hotel-uploads'));
			}
			return item;
		});

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

exports.getHotel = async (req, res, next) => {
	try {
		const { checkIn, checkOut } = req.query;

		let roomAttributes = ['id', 'name', 'price'];

		if (checkIn && checkOut) {
			//Don't Mind Red Lines if using javascript with `Check Javascript` as it check types
			roomAttributes = [
				...roomAttributes,
				[
					sequelize.literal(`(
								CASE 
									WHEN '${checkIn || ''}' != '' AND '${checkOut || ''}' != '' THEN
										CASE 
											WHEN EXISTS (
												SELECT 1 
												FROM hotel_bookings 
												WHERE hotel_bookings.hotel_room_id = rooms.id
												AND (
													(hotel_bookings.check_in BETWEEN '${checkIn}' AND '${checkOut}')
													OR (hotel_bookings.check_out BETWEEN '${checkIn}' AND '${checkOut}')
													OR (hotel_bookings.check_in <= '${checkIn}' AND hotel_bookings.check_out >= '${checkOut}')
												)
											) THEN true
											ELSE false
										END
									ELSE false
								END
							)`),
					'isSoldOut',
				],
			];
		}

		let include = [
			{
				model: HotelImages,
				as: 'images',
				attributes: ['image'],
			},
			{
				model: HotelRooms,
				as: 'rooms',
				attributes: roomAttributes,
				include: [
					{
						model: HotelRoomImages,
						as: 'images',
						attributes: ['image'],
					},
				],
			},
		];

		let hotel = await Hotels.findByPk(req.params.id, {
			include,
			attributes: {
				include: [
					[
						sequelize.literal(`(
							SELECT AVG(rating)
							FROM hotel_reviews
							WHERE hotel_reviews.hotel_id = Hotels.id
						)`),
						'averageRating',
					],
					[
						sequelize.literal(`(
							SELECT COUNT(*)
							FROM hotel_reviews
							WHERE hotel_reviews.hotel_id = Hotels.id
						)`),
						'reviewCount',
					],
				],
			},
		});

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

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

		if (hotel.images && hotel.images.length > 0) {
			hotel.images = hotel.images.map((img) => formatImage(img.image, 'hotel-uploads'));
		}

		if (hotel.rooms && hotel.rooms.length > 0) {
			if (!checkIn || !checkOut) {
				hotel.isSoldOut = 0;
			}

			hotel.rooms = hotel.rooms.map((room) => {
				if (room.images && room.images.length > 0) {
					room.images = room.images.map((img) => formatImage(img.image, 'hotel-uploads'));
				}
				return room;
			});
		}

		let offers = await getHotelOffers({
			id: {
				[Op.ne]: hotel.id,
			},
		});

		offers = formatCase(offers);

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

async function getHotelOffers(whereClause) {
	let offers = await Hotels.findAll({
		where: whereClause,
		include: [
			{
				model: HotelImages,
				as: 'images',
				attributes: ['image'],
			},
		],
		attributes: [
			'id',
			'name',
			'location',
			[
				sequelize.literal(`(
                            SELECT AVG(rating)
                            FROM hotel_reviews
                            WHERE hotel_reviews.hotel_id = Hotels.id
                        )`),
				'averageRating',
			],
			[
				sequelize.literal(`(
                            SELECT COUNT(*)
                            FROM hotel_reviews
                            WHERE hotel_reviews.hotel_id = Hotels.id
                        )`),
				'reviewCount',
			],
			[
				sequelize.literal(`(
                            SELECT MIN(price)
                            FROM hotel_rooms
                            WHERE hotel_rooms.hotel_id = Hotels.id
                        )`),
				'minPrice',
			],
			[
				sequelize.literal(`(
                            SELECT MAX(price)
                            FROM hotel_rooms
                            WHERE hotel_rooms.hotel_id = Hotels.id
                        )`),
				'maxPrice',
			],
		],
		limit: 5,
	});

	offers = offers.map((hotel) => {
		let item = hotel.get({ plain: true });
		if (item.images && item.images.length > 0) {
			item.images = item.images.map((img) => formatImage(img.image, 'hotel-uploads'));
		}
		return item;
	});

	return offers;
}

exports.getHotelRoom = async (req, res, next) => {
	try {
		const member = await optionalAuth(req);
		let include = [
			{
				model: HotelRoomImages,
				as: 'images',
				attributes: ['image'],
			},
		];

		if (member?.id) {
			include.push({
				model: HotelBookings,
				as: 'bookings',
				where: { member_id: member.id, status: 'Completed' },
				attributes: ['id', 'check_in', 'check_out'],
				required: false,
				include: [
					{
						model: HotelReviews,
						as: 'review',
						attributes: ['id', 'rating', 'review', 'is_anonymous'],
						include: [
							{
								model: HotelReviewImages,
								as: 'images',
								attributes: ['image'],
							},
						],
					},
				],
			});
		}

		const room = await HotelRooms.findByPk(req.params.id, {
			include,
		});

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

		let item = room.get({ plain: true });
		if (item.images && item.images.length > 0) {
			item.images = item.images.map((img) => formatImage(img.image, 'hotel-uploads'));
		}

		item.myBookings = item?.bookings ?? null;
		delete item.bookings;

		if (item.myBookings && item.myBookings.length > 0) {
			item.myBookings = item.myBookings.map((booking) => {
				if (booking.review && booking.review.images && booking.review.images.length > 0) {
					booking.review.images = booking.review.images.map((img) => formatImage(img.image, 'hotel-uploads'));
				}
				return booking;
			});
		}

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

exports.checkBookHotelRoom = async (req, res, next) => {
	try {
		const { hotelId, roomId, checkIn, checkOut } = req.body;

		const hotel = await Hotels.findByPk(hotelId);
		if (!hotel) {
			return errorHandler('Hotel not found', 404);
		}

		const room = await HotelRooms.findOne({
			where: { id: roomId, hotel_id: hotelId },
		});
		if (!room) {
			return errorHandler('Room not found in this hotel', 404);
		}

		const checkInDate = new Date(checkIn);
		const checkOutDate = new Date(checkOut);
		const today = new Date();
		today.setHours(0, 0, 0, 0);

		if (checkInDate < today) {
			return errorHandler('Check-in date cannot be in the past', 400);
		}

		if (checkOutDate <= checkInDate) {
			return errorHandler('Check-out date must be after check-in date', 400);
		}

		const existingBooking = await HotelBookings.findOne({
			where: {
				hotel_room_id: roomId,
				[Op.or]: [
					{
						check_in: {
							[Op.between]: [checkIn, checkOut],
						},
					},
					{
						check_out: {
							[Op.between]: [checkIn, checkOut],
						},
					},
					{
						[Op.and]: [{ check_in: { [Op.lte]: checkIn } }, { check_out: { [Op.gte]: checkOut } }],
					},
				],
			},
		});

		if (existingBooking) {
			return errorHandler('Room is not available for the selected dates', 400);
		}

		const duration = calculateNights(checkIn, checkOut);
		const total = parseFloat(room.price) * duration;

		return res.status(201).json({
			success: true,
			message: 'Hotel room booked successfully',
			data: {
				total,
			},
		});
	} catch (error) {
		next(error);
	}
};

exports.bookHotelRoom = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { roomId, checkIn, checkOut, payment = 0, paymentMethod } = req.body;
		const { memberId } = req;

		const room = await HotelRooms.findOne({
			where: { id: roomId },
		});

		if (!room) {
			return errorHandler('Room not found in this hotel', 404);
		}

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

		const checkInDate = new Date(checkIn);
		const checkOutDate = new Date(checkOut);
		const today = new Date();
		today.setHours(0, 0, 0, 0);

		if (checkInDate < today) {
			return errorHandler('Check-in date cannot be in the past', 400);
		}

		if (checkOutDate <= checkInDate) {
			return errorHandler('Check-out date must be after check-in date', 400);
		}

		const existingBooking = await getExistingBookings(roomId, checkIn, checkOut);

		if (existingBooking) {
			return errorHandler('Room is not available for the selected dates', 400);
		}

		const duration = Math.ceil((checkOutDate.getTime() - checkInDate.getTime()) / (1000 * 60 * 60 * 24));
		const total = parseFloat(room.price) * duration;

		let payment_status = 'Unpaid';
		if (payment >= total) {
			payment_status = 'Paid';
		} else if (payment !== total) {
			payment_status = 'Partially Paid';
		}
		const balance = total - payment;

		const booking = await HotelBookings.create(
			{
				hotel_id: hotel.id,
				hotel_room_id: roomId,
				member_id: memberId,
				check_in: checkIn,
				check_out: checkOut,
				price: room.price,
				reference_no: await generateReferenceNumber(HotelBookings),
				payment,
				payment_status,
				payment_method: paymentMethod,
				balance,
				duration,
				total,
			},
			{ transaction },
		);

		await transaction.commit();

		return res.status(201).json({
			success: true,
			message: 'Hotel room booked successfully',
			data: formatCase(booking.get({ plain: true })),
		});
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.payBooking = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { bookingId, payment = 0, paymentMethod } = req.body;

		const booking = await HotelBookings.findOne({
			where: { id: bookingId },
		});

		if (!booking) {
			return errorHandler('Booking not found or does not belong to you', 404);
		}

		if (booking.status === 'Cancelled' || booking.status === 'Completed') {
			return errorHandler('Booking is already cancelled or completed', 400);
		}

		if (booking.payment_status === 'Paid') {
			return errorHandler('Booking is already paid', 400);
		}

		const total = Number(booking.total);
		const totalPayment = Number(booking.payment) + payment;
		let payment_status = 'Unpaid';
		if (totalPayment >= total) {
			payment_status = 'Paid';
		} else if (totalPayment !== total) {
			payment_status = 'Partially Paid';
		}
		const balance = total - Number(payment);

		booking.payment = Number(payment) + Number(booking.payment);
		booking.payment_status = payment_status;
		booking.payment_method = paymentMethod;
		booking.balance = balance;
		booking.status = payment_status === 'Paid' ? 'Pending' : booking.status;
		await booking.save({ transaction });

		await transaction.commit();

		return res.status(201).json({
			success: true,
			message: 'Hotel room paid successfully.',
			data: formatCase(booking.get({ plain: true })),
		});
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.myBookings = async (req, res, next) => {
	try {
		const { memberId } = req;
		const bookings = await HotelBookings.findAll({
			attributes: ['id', 'hotel_id', 'hotel_room_id', 'check_in', 'check_out', 'price', 'status', 'total'],
			where: { member_id: memberId },
			include: [
				{
					model: HotelRooms,
					as: 'room',
					attributes: ['id', 'name'],
					include: [
						{
							model: HotelRoomImages,
							as: 'images',
							attributes: ['image'],
							limit: 1,
						},
					],
				},
			],
			order: [['createdAt', 'DESC']],
		});

		const formattedBookings = [];

		for (const booking of bookings) {
			const item = booking.get({ plain: true });

			if (item.room && item.room.images && item.room.images.length > 0) {
				item.images = item.room.images.map((img) => formatImage(img.image, 'hotel-uploads'));
				item.roomName = item.room.name;
			}

			delete item.room;
			delete item.hotel_room_id;
			delete item.hotel_id;

			if (booking.status === 'Pending') {
				const now = new Date();
				if (new Date(item.check_out) < now) {
					item.status = 'Completed';
					await booking.update({ status: 'Completed' });
				}
			}

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

exports.myBooking = async (req, res, next) => {
	try {
		const { memberId } = req;
		const bookings = await HotelBookings.findAll({
			where: { member_id: memberId, id: req.params.id },
			include: [
				{
					model: Hotels,
					as: 'hotel',
					attributes: ['id', 'name', 'location'],
					include: [
						{
							model: HotelImages,
							as: 'images',
							attributes: ['image'],
							limit: 1,
						},
					],
				},
				{
					model: HotelRooms,
					as: 'room',
					attributes: ['id', 'name'],
				},
			],
			order: [['createdAt', 'DESC']],
		});

		const formattedBookings = bookings.map((booking) => {
			let item = booking.get({ plain: true });
			if (item.hotel && item.hotel.images && item.hotel.images.length > 0) {
				item.hotel.images = item.hotel.images.map((img) => formatImage(img.image, 'hotel-uploads'));
			}
			return item;
		});
		return res.status(200).json({
			success: true,
			data: formatCase(formattedBookings),
		});
	} catch (error) {
		next(error);
	}
};

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

		let bookings = await HotelBookings.findAll({
			where: { member_id: memberId },
			include: [
				{
					model: Hotels,
					as: 'hotel',
					attributes: ['id', 'name', 'location'],
					include: [
						{
							model: HotelImages,
							as: 'images',
							attributes: ['image'],
							limit: 1,
						},
					],
				},
				{
					model: HotelRooms,
					as: 'room',
					attributes: ['id', 'name'],
				},
			],
			order: [['createdAt', 'DESC']],
		});

		bookings = bookings.map((booking) => {
			let item = booking.get({ plain: true });
			if (item.hotel && item.hotel.images && item.hotel.images.length > 0) {
				item.hotel.images = item.hotel.images.map((img) => ({
					...img,
					image: formatImage(img.image, 'hotel-uploads'),
				}));
			}
			return item;
		});

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

exports.createRoomReview = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { bookingId, rating, review } = 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 found', 404);
		}

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

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

		const record = await HotelReviews.create(
			{
				booking_id: booking.id,
				hotel_room_id: booking.hotel_room_id || null,
				member_id: memberId,
				rating,
				review,
			},
			{ transaction },
		);

		let images = [];
		if (req.files && req.files.reviewImages) {
			const imageData = req.files.reviewImages.map((file) => ({
				hotel_review_id: record.id,
				image: file.filename,
			}));
			images = await HotelReviewImages.bulkCreate(imageData, { transaction });
		}

		await transaction.commit();

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

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

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

		const record = await HotelReviews.findOne({
			where: { id, member_id: memberId },
		});
		if (!record) {
			return res.status(404).json({ success: false, message: 'Review not found or not yours' });
		}

		record.rating = rating !== undefined ? rating : record.rating;
		record.review = review !== undefined ? review : record.review;
		await record.save({ transaction });

		let images = [];
		if (req.files && req.files.reviewImages) {
			await HotelReviewImages.destroy({ where: { hotel_review_id: record.id }, transaction });
			const imageData = req.files.reviewImages.map((file) => ({
				hotel_review_id: record.id,
				image: file.filename,
			}));
			images = await HotelReviewImages.bulkCreate(imageData, { transaction });
		} else {
			images = await HotelReviewImages.findAll({ where: { hotel_review_id: record.id }, transaction });
		}

		await transaction.commit();

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

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

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

		const review = await HotelReviews.findOne({
			where: { id, member_id: memberId },
		});
		if (!review) {
			return res.status(404).json({ success: false, message: 'Review not found or not yours' });
		}

		await HotelReviewImages.destroy({ where: { hotel_review_id: id }, transaction });
		await review.destroy({ transaction });

		await transaction.commit();

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