from datetime import datetime, time, timedelta
from django.utils import timezone
from django.db.models import Q
from .models import Appointment, BusinessHours, StaffMember


def get_available_slots(date, service, staff=None, duration=None):
    """
    Get available time slots for a given date, service, and optional staff member.
    
    Args:
        date (date): The date to check for availability
        service (Service): The service to book
        staff (StaffMember, optional): Specific staff member to check. If None, checks all staff.
        duration (timedelta, optional): Duration of the appointment. If None, uses service duration.
    
    Returns:
        list: List of available time slots as (start_time, end_time) tuples
    """
    # Use service duration if no duration provided
    duration = duration or service.duration
    
    # Get the day of the week (0=Monday, 6=Sunday)
    day_of_week = date.weekday()
    day_name = [
        'monday', 'tuesday', 'wednesday', 
        'thursday', 'friday', 'saturday', 'sunday'
    ][day_of_week]
    
    try:
        # Get business hours for this day
        business_hours = BusinessHours.objects.get(day=day_name)
        
        # If business is closed, return empty list
        if not business_hours.is_open:
            return []
            
        # Get business open and close times
        open_time = business_hours.open_time
        close_time = business_hours.close_time
        
    except BusinessHours.DoesNotExist:
        # If no business hours set for this day, assume 24/7 availability
        open_time = time(0, 0)
        close_time = time(23, 59, 59)
    
    # Convert to datetime for easier manipulation
    start_datetime = datetime.combine(date, open_time)
    end_datetime = datetime.combine(date, close_time)
    
    # If close time is before open time (overnight), add a day to close time
    if close_time <= open_time:
        end_datetime += timedelta(days=1)
    
    # Get all staff members who provide this service
    if staff:
        staff_members = [staff]
    else:
        staff_members = StaffMember.objects.filter(
            services=service,
            is_active=True
        )
    
    # If no staff members provide this service, return empty list
    if not staff_members.exists():
        return []
    
    # Get all existing appointments for the given date and staff members
    appointments = Appointment.objects.filter(
        staff_member__in=staff_members,
        start_time__date=date,
        status__in=['pending', 'confirmed']
    )
    
    # Generate time slots
    time_slots = []
    current_time = start_datetime
    
    while current_time + duration <= end_datetime:
        slot_end = current_time + duration
        
        # Check if this time slot is available for any staff member
        slot_available = False
        
        for staff_member in staff_members:
            # Check for overlapping appointments for this staff member
            overlapping = appointments.filter(
                staff_member=staff_member,
                start_time__lt=slot_end,
                end_time__gt=current_time
            ).exists()
            
            if not overlapping:
                slot_available = True
                break
        
        if slot_available:
            time_slots.append((current_time, slot_end))
        
        # Move to next time slot (15-minute intervals)
        current_time += timedelta(minutes=15)
    
    return time_slots


def is_time_slot_available(staff_member, start_time, end_time, exclude_appointment_id=None):
    """
    Check if a specific time slot is available for a staff member.
    
    Args:
        staff_member (StaffMember): The staff member to check
        start_time (datetime): Start time of the desired slot
        end_time (datetime): End time of the desired slot
        exclude_appointment_id (int, optional): ID of an appointment to exclude (for updates)
    
    Returns:
        bool: True if the time slot is available, False otherwise
    """
    # Check for overlapping appointments
    overlapping = Appointment.objects.filter(
        staff_member=staff_member,
        start_time__lt=end_time,
        end_time__gt=start_time,
        status__in=['pending', 'confirmed']
    )
    
    # Exclude the current appointment when updating
    if exclude_appointment_id:
        overlapping = overlapping.exclude(pk=exclude_appointment_id)
    
    return not overlapping.exists()


def get_staff_availability(staff_member, start_date, end_date):
    """
    Get a staff member's availability for a date range.
    
    Args:
        staff_member (StaffMember): The staff member to check
        start_date (date): Start date of the range
        end_date (date): End date of the range (inclusive)
    
    Returns:
        dict: Dictionary with date keys and list of available time slots
    """
    availability = {}
    current_date = start_date
    
    while current_date <= end_date:
        # Get business hours for this day
        day_of_week = current_date.weekday()
        day_name = [
            'monday', 'tuesday', 'wednesday', 
            'thursday', 'friday', 'saturday', 'sunday'
        ][day_of_week]
        
        try:
            business_hours = BusinessHours.objects.get(day=day_name)
            
            if business_hours.is_open:
                # Get appointments for this day
                appointments = Appointment.objects.filter(
                    staff_member=staff_member,
                    start_time__date=current_date,
                    status__in=['pending', 'confirmed']
                ).order_by('start_time')
                
                # Generate available time slots
                available_slots = []
                
                # Start with the full business day
                open_time = datetime.combine(current_date, business_hours.open_time)
                close_time = datetime.combine(current_date, business_hours.close_time)
                
                # If close time is before open time (overnight), add a day to close time
                if business_hours.close_time <= business_hours.open_time:
                    close_time += timedelta(days=1)
                
                # If no appointments, the whole day is available
                if not appointments.exists():
                    available_slots.append({
                        'start': open_time,
                        'end': close_time
                    })
                else:
                    # Add time before first appointment
                    first_appointment = appointments.first()
                    if first_appointment.start_time > open_time:
                        available_slots.append({
                            'start': open_time,
                            'end': first_appointment.start_time
                        })
                    
                    # Add time between appointments
                    for i in range(len(appointments) - 1):
                        current_end = appointments[i].end_time
                        next_start = appointments[i + 1].start_time
                        
                        if current_end < next_start:
                            available_slots.append({
                                'start': current_end,
                                'end': next_start
                            })
                    
                    # Add time after last appointment
                    last_appointment = appointments.last()
                    if last_appointment.end_time < close_time:
                        available_slots.append({
                            'start': last_appointment.end_time,
                            'end': close_time
                        })
                
                availability[current_date.isoformat()] = available_slots
            else:
                availability[current_date.isoformat()] = []
                
        except BusinessHours.DoesNotExist:
            # If no business hours set, assume 24/7 availability
            availability[current_date.isoformat()] = [{
                'start': datetime.combine(current_date, time(0, 0)),
                'end': datetime.combine(current_date, time(23, 59, 59))
            }]
        
        current_date += timedelta(days=1)
    
    return availability
