/**
 * Mawidi Demo Scheduler - Date Utilities
 * Comprehensive date and time manipulation functions
 */

import type {
  BookingDate,
  TimeSlot,
  DateRange,
  WorkingHours,
} from "../types/scheduler.types";
import {
  SCHEDULER_CONFIG,
  TIME_SLOT_INTERVAL,
} from "../config/schedulerConfig";

/**
 * Get array of available booking dates within the booking window
 * Excludes dates that don't meet the minimum advance booking requirement
 *
 * @returns Array of BookingDate objects
 */
export function getAvailableDates(): BookingDate[] {
  const { maxAdvanceBookingDays, minAdvanceBookingHours } = SCHEDULER_CONFIG;
  const now = new Date();
  const minBookingTime = new Date(
    now.getTime() + minAdvanceBookingHours * 60 * 60 * 1000,
  );

  const availableDates: BookingDate[] = [];

  // Generate dates for the next N days
  for (let i = 0; i <= maxAdvanceBookingDays; i++) {
    const date = new Date(now);
    date.setDate(date.getDate() + i);
    date.setHours(0, 0, 0, 0); // Reset to start of day

    const dayOfWeek = date.getDay();
    const workingHours = getWorkingHoursForDay(dayOfWeek);

    // Skip if no working hours for this day
    if (!workingHours) {
      continue;
    }

    // Check if this date has any available slots after min advance time
    const lastSlotTime = parseTimeString(workingHours.end);
    const dayEnd = new Date(date);
    dayEnd.setHours(lastSlotTime.hours, lastSlotTime.minutes, 0, 0);

    // Only include if the last slot of the day is after min booking time
    if (dayEnd > minBookingTime || i > 0) {
      const timeSlots = getTimeSlotsForDate(date, 30); // Check with 30min slots
      const availableSlots = timeSlots.filter((slot) => slot.available).length;

      availableDates.push({
        date,
        dateString: formatDateISO(date),
        availableSlots,
        dayOfWeek,
        bookable: availableSlots > 0,
      });
    }
  }

  return availableDates;
}

/**
 * Generate time slots for a specific date and duration
 *
 * @param date - The date to generate slots for
 * @param duration - Meeting duration in minutes (30 or 60)
 * @returns Array of TimeSlot objects
 */
export function getTimeSlotsForDate(date: Date, duration: 30 | 60): TimeSlot[] {
  const dayOfWeek = date.getDay();
  const workingHours = getWorkingHoursForDay(dayOfWeek);

  if (!workingHours) {
    return [];
  }

  const slots: TimeSlot[] = [];
  const startTime = parseTimeString(workingHours.start);
  const endTime = parseTimeString(workingHours.end);

  // Create a date object for the start of working hours
  const slotDate = new Date(date);
  slotDate.setHours(startTime.hours, startTime.minutes, 0, 0);

  // Create end boundary
  const endDate = new Date(date);
  endDate.setHours(endTime.hours, endTime.minutes, 0, 0);

  // Generate slots at TIME_SLOT_INTERVAL intervals
  while (slotDate < endDate) {
    // Check if there's enough time for the meeting duration
    const slotEnd = new Date(slotDate.getTime() + duration * 60 * 1000);
    if (slotEnd > endDate) {
      break; // Not enough time for this duration
    }

    const timeString = formatTime(slotDate);
    const dateTimeISO = slotDate.toISOString();
    const available = isSlotAvailable(slotDate, duration);

    slots.push({
      time: timeString,
      dateTime: dateTimeISO,
      available,
      unavailableReason: available ? undefined : getUnavailableReason(slotDate),
    });

    // Move to next slot
    slotDate.setMinutes(slotDate.getMinutes() + TIME_SLOT_INTERVAL);
  }

  return slots;
}

/**
 * Check if a specific time slot is available for booking
 *
 * @param dateTime - The datetime to check
 * @param duration - Meeting duration in minutes
 * @returns True if slot is available
 */
export function isSlotAvailable(dateTime: Date, duration: 30 | 60): boolean {
  const now = new Date();
  const { minAdvanceBookingHours } = SCHEDULER_CONFIG;
  const minBookingTime = new Date(
    now.getTime() + minAdvanceBookingHours * 60 * 60 * 1000,
  );

  // Check if slot is in the past or too soon
  if (dateTime < minBookingTime) {
    return false;
  }

  // Check if slot is within working hours
  const dayOfWeek = dateTime.getDay();
  const workingHours = getWorkingHoursForDay(dayOfWeek);

  if (!workingHours) {
    return false;
  }

  const timeString = formatTime(dateTime);
  const slotTime = parseTimeString(timeString);
  const startTime = parseTimeString(workingHours.start);
  const endTime = parseTimeString(workingHours.end);

  // Check if slot starts within working hours
  const slotMinutes = slotTime.hours * 60 + slotTime.minutes;
  const startMinutes = startTime.hours * 60 + startTime.minutes;
  const endMinutes = endTime.hours * 60 + endTime.minutes;

  if (slotMinutes < startMinutes || slotMinutes >= endMinutes) {
    return false;
  }

  // Check if slot + duration doesn't exceed working hours
  const slotEndMinutes = slotMinutes + duration;
  if (slotEndMinutes > endMinutes) {
    return false;
  }

  // TODO: Check against booked slots from external calendar
  // This would require integration with Outlook/Google Calendar

  return true;
}

/**
 * Get working hours for a specific day of week
 *
 * @param dayOfWeek - Day of week (0 = Sunday)
 * @returns WorkingHours object or null if day is not available
 */
export function getWorkingHoursForDay(dayOfWeek: number): WorkingHours | null {
  const days = [
    "sunday",
    "monday",
    "tuesday",
    "wednesday",
    "thursday",
    "friday",
    "saturday",
  ] as const;

  const dayName = days[dayOfWeek];
  return SCHEDULER_CONFIG.workingHours[dayName] || null;
}

/**
 * Format date to ISO 8601 date string (YYYY-MM-DD)
 *
 * @param date - Date to format
 * @returns ISO date string
 */
export function formatDateISO(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

/**
 * Format time to HH:mm format (24-hour)
 *
 * @param date - Date object to extract time from
 * @returns Time string in HH:mm format
 */
export function formatTime(date: Date): string {
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  return `${hours}:${minutes}`;
}

/**
 * Format datetime to ISO 8601 string
 *
 * @param date - Date object
 * @returns ISO 8601 datetime string
 */
export function formatDateTimeISO(date: Date): string {
  return date.toISOString();
}

/**
 * Parse time string (HH:mm) to hours and minutes
 *
 * @param timeString - Time in HH:mm format
 * @returns Object with hours and minutes
 */
export function parseTimeString(timeString: string): {
  hours: number;
  minutes: number;
} {
  const [hours, minutes] = timeString.split(":").map(Number);
  return { hours, minutes };
}

/**
 * Combine date and time into a single Date object
 *
 * @param date - Date object
 * @param timeString - Time string in HH:mm format
 * @returns Combined datetime
 */
export function combineDateAndTime(date: Date, timeString: string): Date {
  const { hours, minutes } = parseTimeString(timeString);
  const combined = new Date(date);
  combined.setHours(hours, minutes, 0, 0);
  return combined;
}

/**
 * Get reason why a slot is unavailable
 *
 * @param dateTime - The datetime to check
 * @returns Unavailable reason
 */
function getUnavailableReason(
  dateTime: Date,
): "past" | "booked" | "outside-hours" | "too-soon" {
  const now = new Date();
  const { minAdvanceBookingHours } = SCHEDULER_CONFIG;
  const minBookingTime = new Date(
    now.getTime() + minAdvanceBookingHours * 60 * 60 * 1000,
  );

  if (dateTime < now) {
    return "past";
  }

  if (dateTime < minBookingTime) {
    return "too-soon";
  }

  const dayOfWeek = dateTime.getDay();
  const workingHours = getWorkingHoursForDay(dayOfWeek);

  if (!workingHours) {
    return "outside-hours";
  }

  // Default to outside-hours if not within working hours
  return "outside-hours";
}

/**
 * Check if a date is today
 *
 * @param date - Date to check
 * @returns True if date is today
 */
export function isToday(date: Date): boolean {
  const today = new Date();
  return (
    date.getDate() === today.getDate() &&
    date.getMonth() === today.getMonth() &&
    date.getFullYear() === today.getFullYear()
  );
}

/**
 * Check if a date is tomorrow
 *
 * @param date - Date to check
 * @returns True if date is tomorrow
 */
export function isTomorrow(date: Date): boolean {
  const tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate() + 1);
  return (
    date.getDate() === tomorrow.getDate() &&
    date.getMonth() === tomorrow.getMonth() &&
    date.getFullYear() === tomorrow.getFullYear()
  );
}

/**
 * Get day name for a date
 *
 * @param date - Date object
 * @param language - Language code ('en' or 'ar')
 * @returns Day name
 */
export function getDayName(date: Date, language: "en" | "ar"): string {
  const locale = language === "ar" ? "ar-SA" : "en-US";
  return date.toLocaleDateString(locale, { weekday: "long" });
}

/**
 * Get month name for a date
 *
 * @param date - Date object
 * @param language - Language code ('en' or 'ar')
 * @returns Month name
 */
export function getMonthName(date: Date, language: "en" | "ar"): string {
  const locale = language === "ar" ? "ar-SA" : "en-US";
  return date.toLocaleDateString(locale, { month: "long" });
}

/**
 * Format date for display
 *
 * @param date - Date object
 * @param language - Language code
 * @returns Formatted date string
 */
export function formatDateForDisplay(
  date: Date,
  language: "en" | "ar",
): string {
  if (isToday(date)) {
    return language === "ar" ? "اليوم" : "Today";
  }

  if (isTomorrow(date)) {
    return language === "ar" ? "غدًا" : "Tomorrow";
  }

  const locale = language === "ar" ? "ar-SA" : "en-US";
  return date.toLocaleDateString(locale, {
    weekday: "short",
    month: "short",
    day: "numeric",
  });
}

/**
 * Format time for display (with 12/24 hour format based on language)
 *
 * @param timeString - Time in HH:mm format
 * @param language - Language code
 * @returns Formatted time string
 */
export function formatTimeForDisplay(
  timeString: string,
  language: "en" | "ar",
): string {
  const { hours, minutes } = parseTimeString(timeString);
  const date = new Date();
  date.setHours(hours, minutes, 0, 0);

  const locale = language === "ar" ? "ar-SA" : "en-US";
  const hour12 = language === "en"; // 12-hour for English, 24-hour for Arabic

  return date.toLocaleTimeString(locale, {
    hour: "2-digit",
    minute: "2-digit",
    hour12,
  });
}

/**
 * Get date range for the booking window
 *
 * @returns DateRange object
 */
export function getBookingWindowRange(): DateRange {
  const now = new Date();
  const start = new Date(now);
  start.setHours(0, 0, 0, 0);

  const end = new Date(start);
  end.setDate(end.getDate() + SCHEDULER_CONFIG.maxAdvanceBookingDays);

  return { start, end };
}

/**
 * Calculate duration between two times
 *
 * @param startTime - Start time string (HH:mm)
 * @param endTime - End time string (HH:mm)
 * @returns Duration in minutes
 */
export function calculateDuration(startTime: string, endTime: string): number {
  const start = parseTimeString(startTime);
  const end = parseTimeString(endTime);

  const startMinutes = start.hours * 60 + start.minutes;
  const endMinutes = end.hours * 60 + end.minutes;

  return endMinutes - startMinutes;
}

/**
 * Add minutes to a time string
 *
 * @param timeString - Time in HH:mm format
 * @param minutes - Minutes to add
 * @returns New time string
 */
export function addMinutesToTime(timeString: string, minutes: number): string {
  const { hours, minutes: mins } = parseTimeString(timeString);
  const date = new Date();
  date.setHours(hours, mins, 0, 0);
  date.setMinutes(date.getMinutes() + minutes);
  return formatTime(date);
}

/**
 * Generate a unique booking ID
 *
 * @returns Unique booking ID
 */
export function generateBookingId(): string {
  const timestamp = Date.now().toString(36);
  const random = Math.random().toString(36).substring(2, 9);
  return `mwd-${timestamp}-${random}`;
}

/**
 * Check if date is within the booking window
 *
 * @param date - Date to check
 * @returns True if within booking window
 */
export function isWithinBookingWindow(date: Date): boolean {
  const now = new Date();
  const maxDate = new Date(now);
  maxDate.setDate(maxDate.getDate() + SCHEDULER_CONFIG.maxAdvanceBookingDays);

  return date >= now && date <= maxDate;
}

/**
 * Check if date is a business day (has working hours)
 *
 * @param date - Date to check
 * @returns True if business day
 */
export function isBusinessDay(date: Date): boolean {
  const dayOfWeek = date.getDay();
  const workingHours = getWorkingHoursForDay(dayOfWeek);
  return workingHours !== null;
}

/**
 * Check if time is within business hours
 *
 * @param dateTime - DateTime to check
 * @returns True if within business hours
 */
export function isWithinBusinessHours(dateTime: Date): boolean {
  const dayOfWeek = dateTime.getDay();
  const workingHours = getWorkingHoursForDay(dayOfWeek);

  if (!workingHours) {
    return false;
  }

  const timeString = formatTime(dateTime);
  const slotTime = parseTimeString(timeString);
  const startTime = parseTimeString(workingHours.start);
  const endTime = parseTimeString(workingHours.end);

  const slotMinutes = slotTime.hours * 60 + slotTime.minutes;
  const startMinutes = startTime.hours * 60 + startTime.minutes;
  const endMinutes = endTime.hours * 60 + endTime.minutes;

  return slotMinutes >= startMinutes && slotMinutes < endMinutes;
}

/**
 * Check if booking meets minimum advance time requirement
 *
 * @param dateTime - DateTime to check
 * @returns True if meets minimum advance
 */
export function meetsMinimumAdvance(dateTime: Date): boolean {
  const now = new Date();
  const { minAdvanceBookingHours } = SCHEDULER_CONFIG;
  const minBookingTime = new Date(
    now.getTime() + minAdvanceBookingHours * 60 * 60 * 1000,
  );

  return dateTime >= minBookingTime;
}
