const { QueryTypes, Op } = require('sequelize');
const { ListingImages, sequelize, RecentlyViewedListings, Listings, ListingReviews } = require('../../models');
const { paginate } = require('../../util');
const { parseJsonSafe } = require('../../helpers');

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

		const location_type_array = location_type ? (Array.isArray(location_type) ? location_type : [location_type]) : null;

		let sqlString = `
		SELECT 
			l.id, l.name, l.description, l.price,  
			l.address_country, l.address_city,
			(SELECT ROUND(AVG(lr.rating), 2) FROM listing_reviews lr WHERE lr.listing_id = l.id) AS averageRating
		FROM 
			listings l
		LEFT JOIN
			users u ON u.id = l.host_id 
		WHERE 
			l.is_deleted = false AND u.status = true`;

		const replacements = [];

		if (search) {
			const str = `%` + search + `%`;

			sqlString += ` AND (l.name LIKE ? OR l.description LIKE ? )`;
			replacements.push(str, str);
		}

		if (location_type_array?.length > 0) {
			const conditions = location_type_array.map(() => {
				return `JSON_EXTRACT(location_type,'$') LIKE ?`;
			});

			sqlString += ` AND (${conditions.join(' OR ')})`;
			replacements.push(...location_type_array.map((lt) => `%${lt}%`));
		}

		if (status) {
			sqlString += ` AND l.status = ?`;
			replacements.push(status);
		}

		if (sortBy && sort) {
			if (['averageRating', 'price'].includes(sortBy) && ['ASC', 'DESC'].includes(sort)) {
				sqlString += ` ORDER BY ${sortBy} ${sort}`;
			}
		}

		const listings = await sequelize.query(sqlString, {
			type: QueryTypes.SELECT,
			replacements,
		});

		const listingsImages = await ListingImages.findAll({
			where: {
				listing_id: {
					[Op.in]: listings.map((listing) => listing.id),
				},
			},
			attributes: ['listing_id', 'image'],
		});

		const jsonSafeListings = listings.map((listing) => {
			const listingImages = listingsImages.filter((img) => img.listing_id === listing.id);

			const data = {
				...listing,
				listingImages: listingImages.map((img) => ({
					image: `${process.env.BASE_URL}/public/host-uploads/` + img.image,
				})),
			};

			data.address = data.address_country && data.address_city ? `${data.address_country}, ${data.address_city}` : null;
			delete data.address_city;
			delete data.address_country;

			return data;
		});

		const paginated = paginate(jsonSafeListings, req.query.page, req.query.limit);

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

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

		const [_listing] = await sequelize.query(
			`
            SELECT listings.*, users.firstname, users.lastname, users.id as host_id, users.hosting_date, users.image as host_image, users.about_me,
				(SELECT ROUND(AVG(rating),2) as rating FROM listing_reviews WHERE listing_reviews.listing_id = listings.id) as listingAverageRating,
				(SELECT ROUND(AVG(rating),2) as rating FROM host_reviews WHERE host_reviews.host_id = users.id) as hostAverageRating,
				(SELECT COUNT(*) as hostReviewsCount FROM host_reviews WHERE host_reviews.host_id = users.id) as hostReviewsCount
				FROM listings
				LEFT JOIN users ON users.id = listings.host_id
            WHERE listings.id = ?`,
			{
				type: QueryTypes.SELECT,
				replacements: [listingId],
			},
		);

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

		const images = await ListingImages.findAll({
			where: {
				listing_id: _listing.id,
			},
			attributes: ['image'],
		});

		const listing = {
			id: _listing.id,
			name: _listing.name,
			description: _listing.description,
			address:
				_listing.address_city && _listing.address_country
					? `${_listing.address_country}, ${_listing.address_city}`
					: null,
			longitude: _listing.longitude,
			latitude: _listing.latitude,
			price: _listing.price,
			averageRating: _listing.listingAverageRating,
			minCapacity: _listing.min_capacity,
			maxCapacity: _listing.max_capacity,
			checkInTime: _listing.check_in_time,
			checkOutTime: _listing.check_out_time,
			cancellationPolicy: _listing.cancellation_policy,
			bedroom: _listing.bedroom,
			bathroom: _listing.bathroom,
			kitchen: _listing.kitchen,
			safetyAndSecurity: _listing.safety_and_security,
			guidelines: _listing.guidelines,
			amenities: parseJsonSafe(_listing.amenities),
			services: parseJsonSafe(_listing.services),
			locationType: parseJsonSafe(_listing.location_type),
			listingImages: images.map((img) => ({
				image: `${process.env.BASE_URL}/public/host-uploads/` + img.image,
			})),
			host: {
				fullname: _listing.firstname + ' ' + _listing.lastname,
				id: _listing.host_id,
				hostingDate: _listing.hosting_date,
				aboutMe: _listing.about_me,
				averageRating: _listing.hostAverageRating,
				image: _listing.host_image ? `${process.env.BASE_URL}/public/user-uploads/` + _listing.host_image : null,
				reviewsCount: _listing.hostReviewsCount,
			},
		};

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

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

		const images = await ListingImages.findAll({
			where: {
				listing_id: listingId,
			},
			attributes: ['image', 'category'],
		});

		const uniqueCategories = [...new Set(images.map((img) => img.category))];

		const groupedImages = [];
		for (const category of uniqueCategories) {
			groupedImages.push({
				category,
				images: images
					.filter((img) => img.category === category)
					.map((img) => ({
						image: `${process.env.BASE_URL}/public/host-uploads/` + img.image,
					})),
			});
		}

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

exports.createRecentlyViewed = async (req, res, next) => {
	const { listingId } = req.params;
	const { userId } = req;

	try {
		const listing = await Listings.findByPk(listingId);

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

		await RecentlyViewedListings.create({
			user_id: userId,
			listing_id: listingId,
			host_id: listing.host_id,
		});

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

exports.getRecentlyViewed = async (req, res, next) => {
	const { userId } = req;

	try {
		const recentViews = await RecentlyViewedListings.findAll({
			where: {
				user_id: userId,
			},
			order: [['createdAt', 'DESC']],
			attributes: ['id'],
			include: [
				{
					model: Listings,
					as: 'listing',
					attributes: ['id', 'name', 'description', 'price', 'address_city', 'address_country'],
					include: [
						{
							model: ListingImages,
							as: 'listingImages',
							attributes: ['image'],
						},
						{
							model: ListingReviews,
							as: 'listingReviews',
							attributes: ['rating'],
						},
					],
				},
			],
		});

		const data = recentViews.map((recentView) => {
			const listings = recentView.dataValues.listing.dataValues;

			const listingImages = listings?.listingImages?.map((img) => ({
				image: img?.image ? `${process.env.BASE_URL}/public/host-uploads/` + img.image : null,
			}));

			let averageRating = null;
			if (listings?.listingReviews?.length > 0) {
				averageRating = (
					listings?.listingReviews?.reduce((acc, review) => acc + review.rating, 0) / listings?.listingReviews?.length
				).toFixed(2);
			}
			const address =
				listings.address_city && listings.address_country
					? `${listings.address_country}, ${listings.address_city}`
					: null;

			delete listings.listingImages;
			delete listings.listingReviews;
			delete listings.address_city;
			delete listings.address_country;
			return {
				id: recentView.id,
				listing: {
					...listings,
					listingImages,
					averageRating,
					address,
				},
			};
		});

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

exports.deleteRecentlyViewed = async (req, res, next) => {
	const { recentlyViewedListingId } = req.params;

	if (!recentlyViewedListingId) {
		return res.status(400).json({
			success: false,
			message: 'Id is required',
		});
	}
	try {
		const recentView = await RecentlyViewedListings.findByPk(recentlyViewedListingId);

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

		await recentView.destroy();

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