const TherapistAvailability = require("../../models/TherapistAvailability");
const AvailabilityNotification = require("../../models/AvailabilityNotification");
const Appointment = require("../../models/Appointment");
const User = require("../../models/userModel");
const mongoose = require("mongoose");

// Get therapist's current availability
const getTherapistAvailability = async (req, res) => {
  try {
    const therapistId = req.user.id;
    
    let availability = await TherapistAvailability.findOne({ therapist: therapistId })
      .populate('pendingChanges.adminResponse.approvedBy', 'fullname email');
    
    // If no availability exists, create default one
    if (!availability) {
      availability = await TherapistAvailability.createDefaultAvailability(therapistId);
    }
    
    return res.status(200).json({
      success: true,
      message: "Availability fetched successfully",
      data: availability
    });
    
  } catch (error) {
    console.error("Get availability error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Update weekly availability (requires admin approval)
const updateWeeklyAvailability = async (req, res) => {
  try {
    const therapistId = req.user.id;
    const { weeklyAvailability, reason } = req.body;
    
    // Validate input
    if (!Array.isArray(weeklyAvailability) || weeklyAvailability.length !== 7) {
      return res.status(400).json({
        success: false,
        message: "Weekly availability must contain exactly 7 days"
      });
    }
    
    // Validate each day
    const validDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
    for (const day of weeklyAvailability) {
      if (!validDays.includes(day.dayOfWeek)) {
        return res.status(400).json({
          success: false,
          message: `Invalid day: ${day.dayOfWeek}`
        });
      }
      
      // Validate time slots
      if (day.slots && Array.isArray(day.slots)) {
        for (const slot of day.slots) {
          if (!slot.startTime || !slot.endTime || 
              !/^([01]\d|2[0-3]):([0-5]\d)$/.test(slot.startTime) ||
              !/^([01]\d|2[0-3]):([0-5]\d)$/.test(slot.endTime)) {
            return res.status(400).json({
              success: false,
              message: "Invalid time format. Use HH:mm format"
            });
          }
          
          // Check if end time is after start time
          if (slot.startTime >= slot.endTime) {
            return res.status(400).json({
              success: false,
              message: "End time must be after start time"
            });
          }
        }
      }
    }
    
    let availability = await TherapistAvailability.findOne({ therapist: therapistId });
    
    if (!availability) {
      availability = await TherapistAvailability.createDefaultAvailability(therapistId);
    }
    
    // Check for conflicts with existing appointments
    const conflicts = await checkAvailabilityConflicts(therapistId, weeklyAvailability);
    
    if (conflicts.length > 0) {
      return res.status(400).json({
        success: false,
        message: "Availability conflicts with existing appointments",
        conflicts
      });
    }
    
    // Create pending change request
    const pendingChange = {
      changeType: 'modify_slots',
      changeData: {
        weeklyAvailability,
        previousAvailability: availability.weeklyAvailability
      },
      reason: reason || 'Weekly availability update',
      status: 'pending'
    };
    
    availability.pendingChanges.push(pendingChange);
    await availability.save();
    
    // Create notification for admin
    const admins = await User.find({ role: 'admin' });
    for (const admin of admins) {
      await AvailabilityNotification.createAvailabilityChangeNotification({
        recipientId: admin._id,
        senderId: therapistId,
        type: 'availability_change_pending',
        availabilityId: availability._id,
        pendingChangeId: pendingChange._id,
        metadata: { changeType: 'weekly_availability' }
      });
    }
    
    return res.status(200).json({
      success: true,
      message: "Availability change submitted for approval",
      data: {
        pendingChangeId: pendingChange._id,
        status: 'pending_approval'
      }
    });
    
  } catch (error) {
    console.error("Update availability error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Add one-time availability slot
const addOneTimeSlot = async (req, res) => {
  try {
    const therapistId = req.user.id;
    const { specificDate, startTime, endTime, reason } = req.body;
    
    // Validate input
    if (!specificDate || !startTime || !endTime) {
      return res.status(400).json({
        success: false,
        message: "Date, start time, and end time are required"
      });
    }
    
    // Validate time format
    if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(startTime) ||
        !/^([01]\d|2[0-3]):([0-5]\d)$/.test(endTime)) {
      return res.status(400).json({
        success: false,
        message: "Invalid time format. Use HH:mm format"
      });
    }
    
    // Validate date
    const slotDate = new Date(specificDate);
    if (isNaN(slotDate.getTime()) || slotDate < new Date()) {
      return res.status(400).json({
        success: false,
        message: "Invalid date or date is in the past"
      });
    }
    
    // Check if end time is after start time
    if (startTime >= endTime) {
      return res.status(400).json({
        success: false,
        message: "End time must be after start time"
      });
    }
    
    let availability = await TherapistAvailability.findOne({ therapist: therapistId });
    
    if (!availability) {
      availability = await TherapistAvailability.createDefaultAvailability(therapistId);
    }
    
    // Check for conflicts with existing appointments
    const existingAppointment = await Appointment.findOne({
      doctor: therapistId,
      date: slotDate,
      'timeSlot.startTime': startTime,
      'timeSlot.endTime': endTime,
      status: { $in: ['scheduled', 'completed'] }
    });
    
    if (existingAppointment) {
      return res.status(400).json({
        success: false,
        message: "This time slot conflicts with an existing appointment"
      });
    }
    
    // Create pending change request
    const pendingChange = {
      changeType: 'add_slots',
      changeData: {
        oneTimeSlot: {
          specificDate: slotDate,
          startTime,
          endTime,
          isRecurring: false,
          status: 'pending_approval'
        }
      },
      reason: reason || 'One-time slot addition',
      status: 'pending'
    };
    
    availability.pendingChanges.push(pendingChange);
    await availability.save();
    
    // Create notification for admin
    const admins = await User.find({ role: 'admin' });
    for (const admin of admins) {
      await AvailabilityNotification.createAvailabilityChangeNotification({
        recipientId: admin._id,
        senderId: therapistId,
        type: 'availability_change_pending',
        availabilityId: availability._id,
        pendingChangeId: pendingChange._id,
        relatedDate: slotDate,
        timeSlots: [{ startTime, endTime }],
        metadata: { changeType: 'one_time_slot' }
      });
    }
    
    return res.status(200).json({
      success: true,
      message: "One-time slot submitted for approval",
      data: {
        pendingChangeId: pendingChange._id,
        status: 'pending_approval'
      }
    });
    
  } catch (error) {
    console.error("Add one-time slot error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Mark dates as unavailable
const markUnavailableDates = async (req, res) => {
  try {
    const therapistId = req.user.id;
    const { dates, reason, isFullDay = true, unavailableSlots = [] } = req.body;
    
    // Validate input
    if (!Array.isArray(dates) || dates.length === 0) {
      return res.status(400).json({
        success: false,
        message: "At least one date is required"
      });
    }
    
    // Validate dates
    const validDates = [];
    for (const dateStr of dates) {
      const date = new Date(dateStr);
      if (isNaN(date.getTime()) || date < new Date()) {
        return res.status(400).json({
          success: false,
          message: `Invalid date: ${dateStr}`
        });
      }
      validDates.push(date);
    }
    
    let availability = await TherapistAvailability.findOne({ therapist: therapistId });
    
    if (!availability) {
      availability = await TherapistAvailability.createDefaultAvailability(therapistId);
    }
    
    // Check for conflicts with existing appointments
    const conflicts = [];
    for (const date of validDates) {
      const existingAppointments = await Appointment.find({
        doctor: therapistId,
        date: date,
        status: { $in: ['scheduled', 'completed'] }
      });
      
      if (existingAppointments.length > 0) {
        conflicts.push({
          date: date.toISOString().split('T')[0],
          appointments: existingAppointments.length
        });
      }
    }
    
    if (conflicts.length > 0) {
      return res.status(400).json({
        success: false,
        message: "Cannot mark dates as unavailable due to existing appointments",
        conflicts
      });
    }
    
    // Create pending change request
    const pendingChange = {
      changeType: 'add_unavailable_date',
      changeData: {
        unavailableDates: validDates.map(date => ({
          date,
          reason: reason || 'Unavailable',
          isFullDay,
          unavailableSlots: isFullDay ? [] : unavailableSlots
        }))
      },
      reason: reason || 'Mark dates as unavailable',
      status: 'pending'
    };
    
    availability.pendingChanges.push(pendingChange);
    await availability.save();
    
    // Create notification for admin
    const admins = await User.find({ role: 'admin' });
    for (const admin of admins) {
      await AvailabilityNotification.createAvailabilityChangeNotification({
        recipientId: admin._id,
        senderId: therapistId,
        type: 'availability_change_pending',
        availabilityId: availability._id,
        pendingChangeId: pendingChange._id,
        metadata: { 
          changeType: 'unavailable_dates',
          dateCount: validDates.length
        }
      });
    }
    
    return res.status(200).json({
      success: true,
      message: "Unavailable dates submitted for approval",
      data: {
        pendingChangeId: pendingChange._id,
        status: 'pending_approval'
      }
    });
    
  } catch (error) {
    console.error("Mark unavailable dates error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Get available slots for a specific date (for client preview)
const getAvailableSlotsForDate = async (req, res) => {
  try {
    const therapistId = req.user.id;
    const { date } = req.query;
    
    if (!date || !date.match(/^\d{4}-\d{2}-\d{2}$/)) {
      return res.status(400).json({
        success: false,
        message: "Valid date is required in 'YYYY-MM-DD' format"
      });
    }
    
    const queryDate = new Date(date);
    if (isNaN(queryDate.getTime())) {
      return res.status(400).json({
        success: false,
        message: "Invalid date format"
      });
    }
    
    const availability = await TherapistAvailability.findOne({ therapist: therapistId });
    
    if (!availability) {
      return res.status(200).json({
        success: true,
        message: "No availability found",
        data: {
          date,
          slots: []
        }
      });
    }
    
    // Get available slots for the date
    const availableSlots = availability.getAvailableSlotsForDate(queryDate);
    
    // Check which slots are already booked
    const bookedAppointments = await Appointment.find({
      doctor: therapistId,
      date: queryDate,
      status: { $in: ['scheduled', 'completed'] }
    }).select('timeSlot');
    
    const bookedSlots = new Set();
    bookedAppointments.forEach(app => {
      bookedSlots.add(`${app.timeSlot.startTime}-${app.timeSlot.endTime}`);
    });
    
    // Filter out booked slots
    const finalSlots = availableSlots
      .filter(slot => !bookedSlots.has(`${slot.startTime}-${slot.endTime}`))
      .map(slot => ({
        startTime: slot.startTime,
        endTime: slot.endTime,
        status: slot.status,
        isRecurring: slot.isRecurring || false
      }));
    
    return res.status(200).json({
      success: true,
      message: "Available slots fetched successfully",
      data: {
        date,
        slots: finalSlots
      }
    });
    
  } catch (error) {
    console.error("Get available slots error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Update availability settings
const updateAvailabilitySettings = async (req, res) => {
  try {
    const therapistId = req.user.id;
    const { settings } = req.body;
    
    // Validate settings
    if (settings.maxSessionsPerDay && (settings.maxSessionsPerDay < 1 || settings.maxSessionsPerDay > 12)) {
      return res.status(400).json({
        success: false,
        message: "Max sessions per day must be between 1 and 12"
      });
    }
    
    if (settings.defaultSessionDuration && ![30, 45, 60, 90, 120].includes(settings.defaultSessionDuration)) {
      return res.status(400).json({
        success: false,
        message: "Invalid session duration"
      });
    }
    
    if (settings.breakBetweenSessions && (settings.breakBetweenSessions < 0 || settings.breakBetweenSessions > 60)) {
      return res.status(400).json({
        success: false,
        message: "Break between sessions must be between 0 and 60 minutes"
      });
    }
    
    let availability = await TherapistAvailability.findOne({ therapist: therapistId });
    
    if (!availability) {
      availability = await TherapistAvailability.createDefaultAvailability(therapistId);
    }
    
    // Update settings
    availability.settings = { ...availability.settings, ...settings };
    await availability.save();
    
    return res.status(200).json({
      success: true,
      message: "Settings updated successfully",
      data: availability.settings
    });
    
  } catch (error) {
    console.error("Update settings error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Get therapist's pending changes
const getPendingChanges = async (req, res) => {
  try {
    const therapistId = req.user.id;
    
    const availability = await TherapistAvailability.findOne({ therapist: therapistId })
      .populate('pendingChanges.adminResponse.approvedBy', 'fullname email');
    
    if (!availability) {
      return res.status(200).json({
        success: true,
        message: "No pending changes found",
        data: []
      });
    }
    
    const pendingChanges = availability.pendingChanges.filter(change => 
      change.status === 'pending'
    );
    
    return res.status(200).json({
      success: true,
      message: "Pending changes fetched successfully",
      data: pendingChanges
    });
    
  } catch (error) {
    console.error("Get pending changes error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

// Helper function to check for conflicts
const checkAvailabilityConflicts = async (therapistId, weeklyAvailability) => {
  const conflicts = [];
  
  // Get current date and next 30 days
  const startDate = new Date();
  const endDate = new Date();
  endDate.setDate(endDate.getDate() + 30);
  
  // Get all appointments in the next 30 days
  const appointments = await Appointment.find({
    doctor: therapistId,
    date: { $gte: startDate, $lte: endDate },
    status: { $in: ['scheduled', 'completed'] }
  });
  
  // Check each appointment against new availability
  for (const appointment of appointments) {
    const appointmentDate = appointment.date;
    const dayOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][appointmentDate.getDay()];
    
    const dayAvailability = weeklyAvailability.find(day => day.dayOfWeek === dayOfWeek);
    
    if (!dayAvailability || !dayAvailability.isAvailable || dayAvailability.isUnavailable) {
      conflicts.push({
        appointmentId: appointment._id,
        date: appointmentDate,
        timeSlot: appointment.timeSlot,
        reason: 'Day marked as unavailable'
      });
      continue;
    }
    
    // Check if specific time slot exists
    const slotExists = dayAvailability.slots.some(slot => 
      slot.startTime === appointment.timeSlot.startTime &&
      slot.endTime === appointment.timeSlot.endTime
    );
    
    if (!slotExists) {
      conflicts.push({
        appointmentId: appointment._id,
        date: appointmentDate,
        timeSlot: appointment.timeSlot,
        reason: 'Time slot no longer available'
      });
    }
  }
  
  return conflicts;
};

// Preview availability changes (client view simulation)
const previewAvailabilityChanges = async (req, res) => {
  try {
    const therapistId = req.user.id;
    const { weeklyAvailability, startDate, endDate } = req.body;
    
    // Validate date range
    const start = new Date(startDate);
    const end = new Date(endDate);
    
    if (isNaN(start.getTime()) || isNaN(end.getTime()) || start >= end) {
      return res.status(400).json({
        success: false,
        message: "Invalid date range"
      });
    }
    
    // Generate preview slots for the date range
    const previewSlots = [];
    const currentDate = new Date(start);
    
    while (currentDate <= end) {
      const dayOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][currentDate.getDay()];
      const dayAvailability = weeklyAvailability.find(day => day.dayOfWeek === dayOfWeek);
      
      if (dayAvailability && dayAvailability.isAvailable && !dayAvailability.isUnavailable) {
        const daySlots = dayAvailability.slots.map(slot => ({
          date: new Date(currentDate),
          startTime: slot.startTime,
          endTime: slot.endTime,
          dayOfWeek
        }));
        previewSlots.push(...daySlots);
      }
      
      currentDate.setDate(currentDate.getDate() + 1);
    }
    
    // Check which slots would conflict with existing appointments
    const appointments = await Appointment.find({
      doctor: therapistId,
      date: { $gte: start, $lte: end },
      status: { $in: ['scheduled', 'completed'] }
    });
    
    const conflicts = [];
    for (const appointment of appointments) {
      const appointmentDateStr = appointment.date.toISOString().split('T')[0];
      const slotExists = previewSlots.some(slot => 
        slot.date.toISOString().split('T')[0] === appointmentDateStr &&
        slot.startTime === appointment.timeSlot.startTime &&
        slot.endTime === appointment.timeSlot.endTime
      );
      
      if (!slotExists) {
        conflicts.push({
          date: appointment.date,
          timeSlot: appointment.timeSlot,
          patientId: appointment.patient
        });
      }
    }
    
    return res.status(200).json({
      success: true,
      message: "Availability preview generated successfully",
      data: {
        previewSlots: previewSlots.length,
        conflicts: conflicts.length,
        conflictDetails: conflicts,
        dateRange: { startDate, endDate }
      }
    });
    
  } catch (error) {
    console.error("Preview availability error:", error);
    return res.status(500).json({
      success: false,
      message: "Server Error",
      error: error.message
    });
  }
};

module.exports = {
  getTherapistAvailability,
  updateWeeklyAvailability,
  addOneTimeSlot,
  markUnavailableDates,
  getAvailableSlotsForDate,
  updateAvailabilitySettings,
  getPendingChanges,
  previewAvailabilityChanges
};
