/**
 * Lunch Protection Utility
 *
 * Ensures that a minimum lunch break is preserved in the middle 50% of the working day.
 * - User journeys: Requires 1h minimum lunch gap (blocks slots that would reduce below 1h)
 * - Admin journeys: Requires 30min minimum, warns if < 1h remaining
 */

export interface TimeSlot {
	startTime: string; // "HH:MM" format
	endTime: string; // "HH:MM" format
}

export interface LunchProtectionResult {
	isBlocked: boolean;
	showWarning: boolean;
	warningMessage?: string;
}

/**
 * Convert "HH:MM" time string to minutes since midnight
 */
export function timeToMinutes(time: string): number {
	const parts = time.split(':');
	const hours = parseInt(parts[0], 10);
	const minutes = parts.length > 1 ? parseInt(parts[1], 10) : 0;
	return hours * 60 + minutes;
}

/**
 * Convert minutes since midnight to "HH:MM" format
 */
export function minutesToTime(minutes: number): string {
	const hours = Math.floor(minutes / 60);
	const mins = minutes % 60;
	return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
}

/**
 * Calculate the middle 50% window of a working day
 * Example: 9:00-17:00 -> middle 50% is 11:00-15:00
 */
export function calculateMiddleWindow(
	dayStartTime: string,
	dayEndTime: string
): { windowStart: number; windowEnd: number } {
	const startMinutes = timeToMinutes(dayStartTime);
	const endMinutes = timeToMinutes(dayEndTime);

	const totalDuration = endMinutes - startMinutes;
	const quarterDuration = Math.floor(totalDuration / 4);

	// Middle 50%: from 25% to 75% of the day
	return {
		windowStart: startMinutes + quarterDuration,
		windowEnd: endMinutes - quarterDuration
	};
}

/**
 * Find all gaps in the middle window after accounting for bookings
 * Returns an array of gap durations in minutes, sorted descending
 */
export function findAllLunchGaps(
	middleWindowStart: number,
	middleWindowEnd: number,
	existingBookings: TimeSlot[],
	proposedBooking?: TimeSlot
): number[] {
	const allBookings: TimeSlot[] = [...existingBookings];
	if (proposedBooking) {
		allBookings.push(proposedBooking);
	}

	const relevantBookings = allBookings
		.filter((booking) => {
			const bookingStart = timeToMinutes(booking.startTime);
			const bookingEnd = timeToMinutes(booking.endTime);
			return bookingStart < middleWindowEnd && bookingEnd > middleWindowStart;
		})
		.map((booking) => ({
			startTime: Math.max(timeToMinutes(booking.startTime), middleWindowStart),
			endTime: Math.min(timeToMinutes(booking.endTime), middleWindowEnd)
		}))
		.sort((a, b) => a.startTime - b.startTime);

	if (relevantBookings.length === 0) {
		return [middleWindowEnd - middleWindowStart];
	}

	const gaps: number[] = [];

	const firstBookingStart = relevantBookings[0].startTime;
	if (firstBookingStart > middleWindowStart) {
		gaps.push(firstBookingStart - middleWindowStart);
	}

	for (let i = 0; i < relevantBookings.length - 1; i++) {
		const gapStart = relevantBookings[i].endTime;
		const gapEnd = relevantBookings[i + 1].startTime;
		if (gapEnd > gapStart) {
			gaps.push(gapEnd - gapStart);
		}
	}

	const lastBookingEnd = relevantBookings[relevantBookings.length - 1].endTime;
	if (lastBookingEnd < middleWindowEnd) {
		gaps.push(middleWindowEnd - lastBookingEnd);
	}

	return gaps.sort((a, b) => b - a);
}

/**
 * Find the largest gap in the middle window after accounting for bookings
 * Returns the duration in minutes of the largest gap
 */
export function findLargestLunchGap(
	middleWindowStart: number,
	middleWindowEnd: number,
	existingBookings: TimeSlot[],
	proposedBooking?: TimeSlot
): number {
	const gaps = findAllLunchGaps(
		middleWindowStart,
		middleWindowEnd,
		existingBookings,
		proposedBooking
	);
	return gaps.length > 0 ? gaps[0] : 0;
}

export const LUNCH_MINIMUM_USER = 60;
export const LUNCH_MINIMUM_ADMIN = 30;
export const LUNCH_WARNING_THRESHOLD = 60;
export const LUNCH_MIN_DAY_DURATION = 5 * 60; // 300 minutes — skip lunch for days ≤ 5h

/**
 * Check lunch protection should apply at all for a day of this duration
 */
export function shouldApplyLunchProtection(dayStartTime: string, dayEndTime: string): boolean {
	const dayDuration = timeToMinutes(dayEndTime) - timeToMinutes(dayStartTime);
	return dayDuration > LUNCH_MIN_DAY_DURATION;
}

/**
 * Check if a proposed booking slot violates lunch protection
 */
export function checkLunchProtection(
	dayStartTime: string,
	dayEndTime: string,
	existingBookings: TimeSlot[],
	proposedSlotStart: string,
	proposedSlotEnd: string,
	isAdmin: boolean
): LunchProtectionResult {
	if (!shouldApplyLunchProtection(dayStartTime, dayEndTime)) {
		return { isBlocked: false, showWarning: false };
	}
	const { windowStart, windowEnd } = calculateMiddleWindow(dayStartTime, dayEndTime);

	const windowDuration = windowEnd - windowStart;
	const minimumRequired = isAdmin ? LUNCH_MINIMUM_ADMIN : LUNCH_MINIMUM_USER;

	if (windowDuration < minimumRequired) {
		return { isBlocked: false, showWarning: false };
	}

	const proposedBooking: TimeSlot = {
		startTime: proposedSlotStart,
		endTime: proposedSlotEnd
	};

	const gapWithout = findLargestLunchGap(windowStart, windowEnd, existingBookings);
	const gapsWith = findAllLunchGaps(windowStart, windowEnd, existingBookings, proposedBooking);
	const gapWith = gapsWith[0] ?? 0;

	if (isAdmin) {
		if (gapWith < LUNCH_MINIMUM_ADMIN && gapWith < gapWithout) {
			return {
				isBlocked: true,
				showWarning: false,
				warningMessage: `This booking would leave no lunch break (minimum 30 minutes required).`
			};
		}

		if (gapWith < LUNCH_WARNING_THRESHOLD && gapWith < gapWithout) {
			const startH = parseInt(proposedSlotStart.split(':')[0], 10);
			const startM = parseInt(proposedSlotStart.split(':')[1], 10);
			const endH = parseInt(proposedSlotEnd.split(':')[0], 10);
			const endM = parseInt(proposedSlotEnd.split(':')[1], 10);
			const fmt = (h: number, m: number) => {
				const p = h >= 12 ? 'PM' : 'AM';
				return `${h % 12 || 12}:${String(m).padStart(2, '0')} ${p}`;
			};

			const viableGaps = gapsWith.filter((g) => g >= LUNCH_MINIMUM_ADMIN);
			if (viableGaps.length >= 2 && gapsWith.every((g) => g >= LUNCH_MINIMUM_ADMIN)) {
				const sorted = [...viableGaps].sort((a, b) => b - a);
				if (sorted[0] === sorted[1]) {
					return {
						isBlocked: false,
						showWarning: true,
						warningMessage: `This booking (${fmt(startH, startM)}–${fmt(endH, endM)}) would split your suggested lunch break into two ${sorted[0]} minute blocks.`
					};
				}
				return {
					isBlocked: false,
					showWarning: true,
					warningMessage: `This booking (${fmt(startH, startM)}–${fmt(endH, endM)}) would split your suggested lunch break into ${sorted[0]} and ${sorted[1]} minute blocks.`
				};
			}

			return {
				isBlocked: false,
				showWarning: true,
				warningMessage: `This booking (${fmt(startH, startM)}–${fmt(endH, endM)}) would reduce lunch break to ${gapWith} minutes.`
			};
		}

		return { isBlocked: false, showWarning: false };
	} else {
		if (gapWith < LUNCH_MINIMUM_USER && gapWith < gapWithout) {
			return {
				isBlocked: true,
				showWarning: false,
				warningMessage: `This booking would leave insufficient lunch break (minimum 1 hour required).`
			};
		}

		return { isBlocked: false, showWarning: false };
	}
}

/**
 * Extract booked slots from working hours and available hours
 * The backend returns available slots (working hours minus bookings)
 * We reverse-engineer the bookings by finding gaps in available slots
 */
export function extractBookedSlots(
	dayStartTime: string,
	dayEndTime: string,
	availableSlots: TimeSlot[]
): TimeSlot[] {
	const dayStart = timeToMinutes(dayStartTime);
	const dayEnd = timeToMinutes(dayEndTime);
	const bookedSlots: TimeSlot[] = [];

	const sortedSlots = [...availableSlots].sort(
		(a, b) => timeToMinutes(a.startTime) - timeToMinutes(b.startTime)
	);

	let currentPos = dayStart;

	for (const slot of sortedSlots) {
		const slotStart = timeToMinutes(slot.startTime);
		const slotEnd = timeToMinutes(slot.endTime);

		// If there's a gap before this slot, it's a booking
		if (slotStart > currentPos) {
			bookedSlots.push({
				startTime: minutesToTime(currentPos),
				endTime: minutesToTime(slotStart)
			});
		}

		currentPos = Math.max(currentPos, slotEnd);
	}

	// Check for booking at the end of the day
	if (currentPos < dayEnd) {
		bookedSlots.push({
			startTime: minutesToTime(currentPos),
			endTime: minutesToTime(dayEnd)
		});
	}

	return bookedSlots;
}

/**
 * Get lunch protection status for all time slots on a given day
 */
export function getLunchProtectionForSlots(
	dayStartTime: string,
	dayEndTime: string,
	existingBookings: TimeSlot[],
	slotDuration: number,
	slotInterval: number,
	isAdmin: boolean
): Map<string, LunchProtectionResult> {
	const results = new Map<string, LunchProtectionResult>();

	const { windowStart, windowEnd } = calculateMiddleWindow(dayStartTime, dayEndTime);

	const windowDuration = windowEnd - windowStart;
	const minimumRequired = isAdmin ? LUNCH_MINIMUM_ADMIN : LUNCH_MINIMUM_USER;

	if (windowDuration < minimumRequired) {
		return results;
	}

	if (!shouldApplyLunchProtection(dayStartTime, dayEndTime)) {
		return results;
	}

	const dayStartMinutes = timeToMinutes(dayStartTime);
	const dayEndMinutes = timeToMinutes(dayEndTime);

	for (
		let slotStart = dayStartMinutes;
		slotStart + slotDuration <= dayEndMinutes;
		slotStart += slotInterval
	) {
		const slotStartStr = minutesToTime(slotStart);
		const slotEndStr = minutesToTime(slotStart + slotDuration);

		const result = checkLunchProtection(
			dayStartTime,
			dayEndTime,
			existingBookings,
			slotStartStr,
			slotEndStr,
			isAdmin
		);

		if (result.isBlocked || result.showWarning) {
			results.set(slotStartStr, result);
		}
	}

	return results;
}
