from datetime import datetime, timedelta

from django import forms
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from .models import Appointment, Service, StaffMember, AppointmentNote, BusinessHours

User = get_user_model()


class AppointmentForm(forms.ModelForm):
    class Meta:
        model = Appointment
        fields = [
            'service', 'staff_member', 'start_time', 'end_time', 
            'status', 'notes'
        ]
        # Client will be set in the view
        widgets = {
            'start_time': forms.DateTimeInput(
                attrs={
                    'type': 'datetime-local',
                    'class': 'form-control datetimepicker-input',
                },
                format='%Y-%m-%dT%H:%M'
            ),
            'end_time': forms.DateTimeInput(
                attrs={
                    'type': 'datetime-local',
                    'class': 'form-control datetimepicker-input',
                },
                format='%Y-%m-%dT%H:%M'
            ),
            'notes': forms.Textarea(attrs={'rows': 3}),
        }
    
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        self.request = kwargs.pop('request', None)
        super().__init__(*args, **kwargs)
        
        # Only show active services and staff members
        self.fields['service'].queryset = Service.objects.filter(is_active=True)
        self.fields['staff_member'].queryset = StaffMember.objects.filter(is_active=True)
        
        # Make staff member field optional
        self.fields['staff_member'].required = False
        
        # Set minimum date for datetime fields to now
        now = timezone.now()
        min_datetime = now.strftime('%Y-%m-%dT%H:%M')
        self.fields['start_time'].widget.attrs['min'] = min_datetime
        self.fields['end_time'].widget.attrs['min'] = min_datetime
        
        # Set initial status to 'pending' for new appointments
        if not self.instance.pk:
            self.fields['status'].initial = 'pending'
    
    def clean(self):
        cleaned_data = super().clean()
        start_time = cleaned_data.get('start_time')
        end_time = cleaned_data.get('end_time')
        service = cleaned_data.get('service')
        staff_member = cleaned_data.get('staff_member')
        
        # If we're updating an existing appointment, get the instance values for missing fields
        if self.instance and self.instance.pk:
            if not start_time:
                start_time = self.instance.start_time
            if not end_time:
                end_time = self.instance.end_time
            if not service:
                service = self.instance.service
            if not staff_member and self.instance.staff_member:
                staff_member = self.instance.staff_member
        
        # Ensure required fields are present
        if not all([start_time, end_time, service]):
            missing_fields = []
            if not start_time:
                missing_fields.append('start_time')
            if not end_time:
                missing_fields.append('end_time')
            if not service:
                missing_fields.append('service')
            if missing_fields:
                raise ValidationError({
                    field: _('This field is required.') for field in missing_fields
                })
        
        # Validate start and end times
        if start_time and end_time:
            if start_time >= end_time:
                raise ValidationError({
                    'end_time': _('End time must be after start time.')
                })
            
            # Check if the time slot is in the past (with 5 minute buffer)
            if start_time < (timezone.now() - timezone.timedelta(minutes=5)):
                raise ValidationError({
                    'start_time': _('Cannot schedule an appointment in the past.')
                })
            
            # If service is provided, calculate the expected end time
            if service:
                expected_end = start_time + service.duration
                if abs((end_time - start_time) - service.duration).total_seconds() > 60:  # 1 minute tolerance
                    end_time = expected_end
                    cleaned_data['end_time'] = end_time
            
            # Get business hours for the start date
            try:
                business_hours = BusinessHours.objects.get(day_of_week=start_time.weekday())
                if not business_hours.is_closed:
                    tz = timezone.get_current_timezone()
                    business_close = timezone.make_aware(
                        datetime.combine(start_time.date(), business_hours.close_time),
                        timezone=tz
                    )
                    # If the appointment would go past closing time, adjust it
                    if end_time > business_close:
                        if start_time >= business_close:
                            raise ValidationError({
                                'start_time': _('Business is closed at this time.')
                            })
                        # Set end time to business close
                        cleaned_data['end_time'] = business_close
                        end_time = business_close
                        messages.warning(
                            self.request,
                            _('Appointment end time was adjusted to business closing time.')
                        )
            except BusinessHours.DoesNotExist:
                pass  # No business hours defined for this day
            
            # Check if staff member is available
            if staff_member and not self._is_staff_available(
                staff_member, 
                start_time, 
                end_time,
                self.instance.pk if self.instance else None
            ):
                raise ValidationError({
                    'staff_member': _('This staff member is not available at the selected time.')
                })
            
            # Set end time based on service duration if not provided or doesn't match
            if service:
                expected_end = start_time + service.duration
                time_diff = abs((end_time - start_time) - service.duration).total_seconds()
                
                # If the end time doesn't match the service duration, update it
                if time_diff > 60:  # 1 minute tolerance
                    cleaned_data['end_time'] = expected_end
                    end_time = expected_end  # Update the local variable for further validation
                    
                    # If this would go past business hours, adjust to close time
                    business_hours = BusinessHours.objects.get(day_of_week=start_time.weekday())
                    if not business_hours.is_closed:
                        tz = start_time.tzinfo
                        business_close = timezone.make_aware(
                            datetime.combine(start_time.date(), business_hours.close_time),
                            timezone=tz
                        )
                        if end_time > business_close:
                            # If the service would go past closing, adjust the start time earlier
                            new_start = business_close - service.duration
                            if new_start >= business_close - timedelta(hours=8):  # At least 8 hours before close
                                cleaned_data['start_time'] = new_start
                                start_time = new_start  # Update the local variable
                                cleaned_data['end_time'] = business_close
                                end_time = business_close
                            else:
                                raise ValidationError({
                                    'start_time': _('Not enough time available before closing. Please choose an earlier time.')
                                })
        
        # If no staff member is selected, find an available one
        if not staff_member and service and start_time and end_time:
            available_staff = StaffMember.objects.filter(
                services=service,
                is_active=True
            )
            
            for staff in available_staff:
                if self._is_staff_available(staff, start_time, end_time, self.instance.pk if self.instance else None):
                    cleaned_data['staff_member'] = staff
                    break
            
            if not cleaned_data.get('staff_member'):
                raise ValidationError({
                    'staff_member': _('No available staff members for the selected time slot.')
                })
        
        return cleaned_data
    
    def _is_within_business_hours(self, start_time, end_time):
        """Check if the time slot is within business hours."""
        try:
            # Check if appointment spans multiple days
            if start_time.date() != end_time.date():
                print("Appointment spans multiple days, checking each day separately")
                current_date = start_time.date()
                end_date = end_time.date()
                
                while current_date <= end_date:
                    day_start = max(
                        timezone.make_aware(datetime.combine(current_date, datetime.min.time())),
                        start_time
                    )
                    day_end = min(
                        timezone.make_aware(datetime.combine(current_date, datetime.max.time())),
                        end_time
                    )
                    
                    if not self._check_single_day_hours(day_start, day_end):
                        return False
                        
                    current_date += timedelta(days=1)
                return True
            else:
                return self._check_single_day_hours(start_time, end_time)
                
        except Exception as e:
            print(f"Error checking business hours: {str(e)}")
            return False
    
    def _check_single_day_hours(self, start_time, end_time):
        """Check if a time slot within a single day is within business hours."""
        try:
            # Get the day of the week (0=Monday, 6=Sunday)
            day_of_week = start_time.weekday()
            day_name = [
                'monday', 'tuesday', 'wednesday', 
                'thursday', 'friday', 'saturday', 'sunday'
            ][day_of_week]
            
            print(f"\n=== Checking business hours for {day_name} ===")
            print(f"Appointment time: {start_time} to {end_time}")
            
            # Get business hours for this day
            business_hours = BusinessHours.objects.get(day_of_week=day_of_week)
            print(f"Business hours: {business_hours.open_time} to {business_hours.close_time}")
            
            if business_hours.is_closed:
                print("Business is closed on this day")
                return False
            
            # Get the timezone from the start_time (which is timezone-aware)
            tz = start_time.tzinfo
            
            # Create timezone-aware datetime objects for comparison
            business_open = timezone.make_aware(
                datetime.combine(start_time.date(), business_hours.open_time),
                timezone=tz
            )
            business_close = timezone.make_aware(
                datetime.combine(start_time.date(), business_hours.close_time),
                timezone=tz
            )
            
            # Handle overnight business hours
            if business_hours.close_time < business_hours.open_time:
                business_close += timedelta(days=1)
                print(f"Overnight hours detected - adjusted close time: {business_close}")
            
            # Check if the time slot is within business hours
            is_within_hours = (business_open <= start_time and end_time <= business_close)
            
            print(f"Business open: {business_open}")
            print(f"Business close: {business_close}")
            print(f"Start time: {start_time}")
            print(f"End time: {end_time}")
            print(f"Is within business hours: {is_within_hours}")
            
            return is_within_hours
            
        except BusinessHours.DoesNotExist:
            # If no business hours are set for this day, assume 24/7 availability
            print("No business hours found for this day, assuming 24/7 availability")
            return True
    
    def _is_staff_available(self, staff_member, start_time, end_time, exclude_appointment_id=None):
        """
        Check if the staff member is available during the given time slot.
        
        Args:
            staff_member: The staff member to check
            start_time: Start time of the desired slot
            end_time: End time of the desired slot
            exclude_appointment_id: ID of an appointment to exclude (for updates)
            
        Returns:
            bool: True if the staff member is available, False otherwise
        """
        # Check for overlapping appointments
        overlapping_appointments = Appointment.objects.filter(
            staff_member=staff_member,
            status__in=['pending', 'confirmed', 'in_progress'],
            start_time__lt=end_time,
            end_time__gt=start_time
        )
        
        # Exclude the current appointment when updating
        if exclude_appointment_id:
            overlapping_appointments = overlapping_appointments.exclude(pk=exclude_appointment_id)
        
        # Check if the staff member is generally available (not on leave, etc.)
        # This is a placeholder for any additional availability checks you might want to add
        is_available = not overlapping_appointments.exists()
        
        return is_available


class DurationInput(forms.NumberInput):
    """Custom widget for duration input in minutes"""
    input_type = 'number'
    template_name = 'django/forms/widgets/number.html'
    
    def format_value(self, value):
        if value is None:
            return None
        if isinstance(value, timedelta):
            # Convert timedelta to total minutes (as integer)
            return int(value.total_seconds() // 60)
        return value


class ServiceForm(forms.ModelForm):
    # Use a custom field for duration that handles the conversion
    duration_minutes = forms.IntegerField(
        min_value=5,
        label='Duration (minutes)',
        help_text='Minimum 5 minutes',
        widget=forms.NumberInput(attrs={
            'class': 'form-control',
            'min': '5',
            'step': '1',
            'placeholder': 'Duration in minutes'
        })
    )
    
    class Meta:
        model = Service
        fields = ['name', 'description', 'price', 'is_active']
        widgets = {
            'description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
            'price': forms.NumberInput(
                attrs={
                    'class': 'form-control',
                    'step': '0.01',
                    'min': '0',
                    'placeholder': '0.00'
                }
            ),
            'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Set initial value for duration_minutes if editing an existing service
        if self.instance and self.instance.pk and self.instance.duration:
            self.fields['duration_minutes'].initial = int(self.instance.duration.total_seconds() // 60)
    
    def clean_duration_minutes(self):
        """Convert minutes to timedelta"""
        minutes = self.cleaned_data.get('duration_minutes')
        if minutes is not None:
            if minutes < 5:
                raise ValidationError(_('Duration must be at least 5 minutes.'))
            return timezone.timedelta(minutes=minutes)
        return None
    
    def save(self, commit=True):
        # Save the duration from our custom field
        service = super().save(commit=False)
        service.duration = self.cleaned_data['duration_minutes']
        if commit:
            service.save()
        return service


class StaffMemberForm(forms.ModelForm):
    class Meta:
        model = StaffMember
        fields = ['user', 'services', 'bio', 'is_active']
        widgets = {
            'bio': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
            'services': forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
            'user': forms.Select(attrs={'class': 'form-select'}),
            'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Only show active services and all active users
        self.fields['services'].queryset = Service.objects.filter(is_active=True)
        self.fields['user'].queryset = User.objects.filter(is_active=True)
        
        # Make the user field required
        self.fields['user'].required = True


class AppointmentNoteForm(forms.ModelForm):
    class Meta:
        model = AppointmentNote
        fields = ['content']
        widgets = {
            'content': forms.Textarea(attrs={
                'rows': 3,
                'class': 'form-control',
                'placeholder': 'Add a note about this appointment...'
            })
        }
    
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        super().__init__(*args, **kwargs)
    
    def save(self, commit=True):
        note = super().save(commit=False)
        if self.user:
            note.author = self.user
        if commit:
            note.save()
        return note


class BusinessHoursForm(forms.ModelForm):
    class Meta:
        model = BusinessHours
        fields = ['day_of_week', 'open_time', 'close_time', 'is_closed']
        widgets = {
            'day_of_week': forms.Select(attrs={'class': 'form-select'}),
            'open_time': forms.TimeInput(
                attrs={
                    'type': 'time',
                    'step': '900',  # 15-minute increments
                    'class': 'form-control',
                },
                format='%H:%M'
            ),
            'close_time': forms.TimeInput(
                attrs={
                    'type': 'time',
                    'step': '900',  # 15-minute increments
                    'class': 'form-control',
                },
                format='%H:%M'
            ),
            'is_closed': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Set required to False for time fields when business is closed
        if self.instance and self.instance.is_closed:
            self.fields['open_time'].required = False
            self.fields['close_time'].required = False
    
    def clean(self):
        cleaned_data = super().clean()
        is_closed = cleaned_data.get('is_closed', False)
        open_time = cleaned_data.get('open_time')
        close_time = cleaned_data.get('close_time')
        
        if not is_closed:
            if not open_time or not close_time:
                raise ValidationError({
                    'open_time': 'Open and close times are required when business is open.',
                    'close_time': 'Open and close times are required when business is open.'
                })
            
            if open_time and close_time and open_time >= close_time:
                raise ValidationError({
                    'close_time': 'Close time must be after open time.'
                })
        
        return cleaned_data
