const { Op } = require('sequelize');
const {
	Bookings,
	sequelize,
	HostReviews,
	RecentlyViewedListings,
	Users,
	Listings,
	HostNotifications,
} = require('../models');
const { calculateNights } = require('../helpers');
const { formatTimestamp } = require('../util');
const moment = require('moment');

exports.getEarningsByDate = async (date, userId) => {
	const startOfDay = `${date} 00:00:00`;
	const endOfDay = `${date} 23:59:59`;

	try {
		const result = await Bookings.findAll({
			attributes: [[sequelize.fn('SUM', sequelize.col('amount_paid')), 'totalEarnings']],
			where: {
				host_id: userId,
				status: 'Done',
				updatedAt: {
					[Op.between]: [startOfDay, endOfDay],
				},
			},
		});

		return parseFloat(result[0]?.dataValues?.totalEarnings || 0);
	} catch (error) {
		// console.error('Error in getEarningsByDate:', error.message);
		throw new Error('Failed to fetch earnings for the date.');
	}
};

exports.getEarningsByMonth = async (month, userId) => {
	const startOfMonth = `${month}-01 00:00:00`;
	const endOfMonth = `${month}-31 23:59:59`;

	const lastDayOfMonth = new Date(month + '-01');
	lastDayOfMonth.setMonth(lastDayOfMonth.getMonth() + 1);
	lastDayOfMonth.setDate(0);

	const endDate = `${month}-${lastDayOfMonth.getDate()} 23:59:59`;

	try {
		const result = await Bookings.findAll({
			attributes: [[sequelize.fn('SUM', sequelize.col('amount_paid')), 'totalEarnings']],
			where: {
				host_id: userId,
				status: 'Done',
				updatedAt: {
					[Op.between]: [startOfMonth, endDate],
				},
			},
		});

		return parseFloat(result[0]?.dataValues?.totalEarnings || 0);
	} catch (error) {
		// console.error('Error in getEarningsByMonth:', error.message);
		throw new Error('Failed to fetch earnings for the month');
	}
};

exports.getHostRating = async (userId) => {
	try {
		const result = await HostReviews.findAll({
			attributes: [[sequelize.fn('AVG', sequelize.col('rating')), 'averageRating']],
			where: {
				host_id: userId,
			},
		});

		return parseFloat(result[0]?.dataValues?.averageRating || 0);
	} catch (error) {
		// console.error('Error in getHostRating:', error.message);
		throw new Error('Failed to fetch host rating');
	}
};

exports.getBookingCountLast30Days = async (userId) => {
	try {
		const thirtyDaysAgo = new Date();
		thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
		const thirtyDaysAgoDate = thirtyDaysAgo.toISOString();

		const result = await Bookings.count({
			where: {
				host_id: userId,
				status: 'Done',
				createdAt: {
					[Op.gte]: thirtyDaysAgoDate,
				},
			},
		});

		return result;
	} catch (error) {
		// console.error('Error in getBookingCountLast30Days:', error.message);
		throw new Error('Failed to fetch booking count for the last 30 days');
	}
};

exports.getTotalViews = async (userId) => {
	try {
		const thirtyDaysAgo = new Date();
		thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
		const thirtyDaysAgoDate = thirtyDaysAgo.toISOString();

		const result = await RecentlyViewedListings.count({
			where: {
				host_id: userId,
				createdAt: {
					[Op.gte]: thirtyDaysAgoDate,
				},
			},
		});

		return result;
	} catch (error) {
		// console.error('Error in getTotalViews:', error.message);
		throw new Error('Failed to fetch total views in the last 30 days');
	}
};

exports.getUpcomingBookings = async (userId) => {
	try {
		const upcomingBookings = await Bookings.findAll({
			where: {
				host_id: userId,
				status: {
					[Op.in]: ['Upcoming', 'Pending'],
				},
			},
			order: [['createdAt', 'DESC']],
			limit: 5,
			include: [
				{
					model: Users,
					as: 'customers',
				},
				{
					model: Listings,
					as: 'listing',
				},
			],
		});

		return upcomingBookings.map((booking) => {
			const numberOfNights = calculateNights(booking.check_in, booking.check_out);

			return {
				id: booking.id,
				customerName: booking.customers
					? `${booking.customers.firstname || ''} ${booking.customers.lastname || ''}`.trim()
					: 'N/A',
				listingName: booking.listing ? booking.listing.name : 'N/A',
				pax: booking.pax,
				checkIn: booking.check_in,
				checkOut: booking.check_out,
				numberOfNights,
				status: booking.status,
			};
		});
	} catch (error) {
		// console.error('Error in getUpcomingBookings:', error.message);
		throw new Error('Failed to fetch upcoming bookings');
	}
};

exports.getBookingsCountByStatus = async (userId, date) => {
	const whereConditions = {
		host_id: userId,
	};

	if (date) {
		const [year, month, day] = date.split('-');
		if (day) {
			whereConditions.createdAt = {
				[Op.between]: [`${year}-${month}-${day} 00:00:00`, `${year}-${month}-${day} 23:59:59`],
			};
		} else if (month) {
			whereConditions.createdAt = {
				[Op.between]: [`${year}-${month}-01 00:00:00`, `${year}-${month}-31 23:59:59`],
			};
		}
	}

	try {
		const statusCounts = await Bookings.findAll({
			attributes: ['status', [sequelize.fn('COUNT', sequelize.col('status')), 'count']],
			where: whereConditions,
			group: ['status'],
			raw: true,
		});

		const allStatuses = ['Done', 'Upcoming', 'Cancelled', 'Pending'];

		const counts = allStatuses.reduce((acc, status) => {
			acc[status] = 0;
			return acc;
		}, {});

		statusCounts.forEach((statusData) => {
			counts[statusData.status] = parseInt(statusData.count, 10);
		});

		return counts;
	} catch (error) {
		// console.error('Error in getBookingsCountByStatus:', error.message);
		throw new Error('Failed to fetch booking counts per status');
	}
};

exports.getOccupancyRate = async (userId) => {
	const today = moment();
	const startDate = today.clone().subtract(30, 'days').startOf('day').format('YYYY-MM-DD 00:00:00'); // 30 days ago at midnight
	const endDate = today.endOf('day').format('YYYY-MM-DD 23:59:59'); // current day until the end of the day

	try {
		const totalListings = await Listings.count({
			where: { host_id: userId },
		});

		if (totalListings === 0) {
			return '0.00%';
		}

		const bookedListings = await Bookings.findAll({
			attributes: ['listing_id'],
			where: {
				host_id: userId,
				check_in: {
					[Op.lte]: endDate,
				},
				check_out: {
					[Op.gte]: startDate,
				},
			},
			group: ['listing_id'],
		});

		const occupancyRate = (bookedListings.length / totalListings) * 100;

		return `${occupancyRate.toFixed(2)}%`;
	} catch (error) {
		// console.error('Error in getOccupancyRate:', error.message);
		throw new Error('Failed to fetch occupancy rate');
	}
};

exports.getBookingConversionRate = async (userId) => {
	const today = moment();
	const startDate = today.clone().subtract(30, 'days').startOf('day').format('YYYY-MM-DD 00:00:00'); // 30 days ago at midnight
	const endDate = today.endOf('day').format('YYYY-MM-DD 23:59:59'); // current day until the end of the day

	try {
		const totalViews = await RecentlyViewedListings.count({
			where: {
				host_id: userId,
				createdAt: {
					[Op.between]: [startDate, endDate],
				},
			},
		});

		if (totalViews === 0) {
			return '0.00%';
		}

		const uniqueBookings = await Bookings.findAll({
			attributes: ['listing_id'],
			where: {
				host_id: userId,
				status: {
					[Op.in]: ['Upcoming', 'Done'],
				},
				check_in: {
					[Op.lte]: endDate,
				},
				check_out: {
					[Op.gte]: startDate,
				},
			},
			group: ['listing_id'],
		});

		const conversionRate = (uniqueBookings.length / totalViews) * 100;

		return `${conversionRate.toFixed(2)}%`;
	} catch (error) {
		// console.error('Error in getBookingConversionRate:', error.message);
		throw new Error('Failed to fetch booking conversion rate');
	}
};

exports.getNotifications = async (userId) => {
	try {
		const notifications = await HostNotifications.findAll({
			where: { host_id: userId },
			order: [['createdAt', 'DESC']],
			attributes: ['id', 'booking_id', 'notif_type', 'message', 'status', 'createdAt'],
			limit: 5,
		});

		const mappedNotifications = notifications.map((notification) => ({
			bookingId: notification.booking_id,
			type: notification.notif_type,
			message: notification.message,
			status: notification.status,
			dateTime: formatTimestamp(notification.createdAt),
		}));

		return mappedNotifications;
	} catch (error) {
		// console.error('Error fetching notifications:', error.message);
		throw new Error('Unable to fetch notifications');
	}
};

exports.getMonthlyOccupancyRate = async (userId) => {
	const currentYear = moment().year();
	const months = Array.from({ length: 12 }, (_, index) => index + 1); // [1, 2, ..., 12]

	try {
		const occupancyRates = await Promise.all(
			months.map(async (month) => {
				const startOfMonth = moment()
					.year(currentYear)
					.month(month - 1)
					.startOf('month')
					.format('YYYY-MM-DD 00:00:00');
				const endOfMonth = moment()
					.year(currentYear)
					.month(month - 1)
					.endOf('month')
					.format('YYYY-MM-DD 23:59:59');

				const totalListings = await Listings.count({
					where: { host_id: userId },
				});

				if (totalListings === 0) {
					return {
						month: moment()
							.month(month - 1)
							.format('MMMM'),
						rate: '0.00%',
					};
				}

				const bookedListings = await Bookings.findAll({
					attributes: ['listing_id'],
					where: {
						host_id: userId,
						check_in: {
							[Op.lte]: endOfMonth,
						},
						check_out: {
							[Op.gte]: startOfMonth,
						},
					},
					group: ['listing_id'],
				});

				const occupancyRate = (bookedListings.length / totalListings) * 100;

				return {
					month: moment()
						.month(month - 1)
						.format('MMMM'),
					rate: `${occupancyRate.toFixed(2)}%`,
				};
			}),
		);

		return occupancyRates;
	} catch (error) {
		throw new Error('Unable to fetch monthly occupancy rates');
	}
};
