from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import (
    ListView, DetailView, CreateView, UpdateView, DeleteView, View, TemplateView
)
from django.urls import reverse_lazy, reverse
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpResponseForbidden, HttpResponse
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Q
from django.utils import timezone
from django.contrib.auth import get_user_model
from datetime import datetime, timedelta
import json

from .models import Appointment, Service, StaffMember, AppointmentNote, BusinessHours
from .forms import AppointmentForm, ServiceForm, StaffMemberForm, BusinessHoursForm, AppointmentNoteForm
from .utils import get_available_slots

User = get_user_model()


class AppointmentListView(LoginRequiredMixin, ListView):
    model = Appointment
    template_name = 'appointments/appointment_list.html'
    context_object_name = 'appointments'
    paginate_by = 10
    
    def get_queryset(self):
        queryset = Appointment.objects.filter(
            Q(client=self.request.user) | Q(staff_member__user=self.request.user)
        ).distinct()
        
        # Filter by status if provided
        status = self.request.GET.get('status')
        if status:
            queryset = queryset.filter(status=status)
            
        # Filter by staff if provided
        staff_id = self.request.GET.get('staff')
        if staff_id:
            queryset = queryset.filter(staff_member_id=staff_id)
            
        # Filter by service if provided
        service_id = self.request.GET.get('service')
        if service_id:
            queryset = queryset.filter(service_id=service_id)
            
        # Filter by date range if provided
        date_from = self.request.GET.get('date_from')
        date_to = self.request.GET.get('date_to')
        
        if date_from:
            try:
                date_from = datetime.strptime(date_from, '%Y-%m-%d').date()
                queryset = queryset.filter(start_time__date__gte=date_from)
            except ValueError:
                pass
                
        if date_to:
            try:
                date_to = datetime.strptime(date_to, '%Y-%m-%d').date()
                queryset = queryset.filter(start_time__date__lte=date_to)
            except ValueError:
                pass
        
        # Order by start time (upcoming first)
        queryset = queryset.order_by('start_time')
        
        return queryset
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['status_filter'] = self.request.GET.get('status', '')
        context['staff_filter'] = self.request.GET.get('staff', '')
        context['service_filter'] = self.request.GET.get('service', '')
        context['date_from'] = self.request.GET.get('date_from', '')
        context['date_to'] = self.request.GET.get('date_to', '')
        
        # Add filters to context
        context['all_staff'] = StaffMember.objects.filter(is_active=True)
        context['all_services'] = Service.objects.filter(is_active=True)
        
        return context


class AppointmentDetailView(LoginRequiredMixin, DetailView):
    model = Appointment
    template_name = 'appointments/appointment_detail.html'
    context_object_name = 'appointment'
    
    def get_queryset(self):
        return Appointment.objects.filter(
            Q(client=self.request.user) | Q(staff_member__user=self.request.user)
        )
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['note_form'] = AppointmentNoteForm()
        return context


class AppointmentCreateView(LoginRequiredMixin, CreateView):
    model = Appointment
    form_class = AppointmentForm
    template_name = 'appointments/appointment_form.html'
    
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'user': self.request.user,
            'request': self.request
        })
        return kwargs
    
    def form_valid(self, form):
        try:
            # Set the client to the current user
            if not self.request.user.is_authenticated:
                messages.error(self.request, 'You must be logged in to create an appointment.')
                return redirect('login')
                
            # Create the appointment instance but don't save it yet
            self.object = form.save(commit=False)
            
            # Set the client
            self.object.client = self.request.user
            
            # Save the appointment
            self.object.save()
            
            # Save many-to-many relationships if any
            form.save_m2m()
            
            messages.success(self.request, 'Appointment scheduled successfully!')
            return redirect(self.get_success_url())
            
        except Exception as e:
            # Log the error
            import logging
            import traceback
            logger = logging.getLogger(__name__)
            logger.error(f"Error creating appointment: {str(e)}\n{traceback.format_exc()}")
            
            # Add error message
            messages.error(
                self.request,
                'An error occurred while saving the appointment. Please check the form and try again.'
            )
            
            # Return form with errors
            return self.form_invalid(form)
    
    def form_invalid(self, form):
        # Log form errors
        import logging
        logger = logging.getLogger(__name__)
        logger.error(f"Form errors: {form.errors}")
        
        # Add error message
        messages.error(
            self.request,
            'Please correct the errors below.'
        )
        return super().form_invalid(form)
    
    def get_success_url(self):
        return reverse('appointment-detail', kwargs={'pk': self.object.pk})


class AppointmentUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Appointment
    form_class = AppointmentForm
    template_name = 'appointments/appointment_form.html'
    
    def test_func(self):
        appointment = self.get_object()
        return (
            self.request.user == appointment.client or 
            self.request.user == appointment.staff.user or
            self.request.user.is_staff
        )
    
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'user': self.request.user,
            'request': self.request
        })
        return kwargs
    
    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, 'Appointment updated successfully!')
        return response
    
    def get_success_url(self):
        return reverse('appointment-detail', kwargs={'pk': self.object.pk})


class AppointmentUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Appointment
    form_class = AppointmentForm
    template_name = 'appointments/appointment_form.html'
    
    def test_func(self):
        appointment = self.get_object()
        return (
            self.request.user == appointment.client or 
            self.request.user.is_staff
        )
    
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'user': self.request.user,
            'request': self.request
        })
        return kwargs
    
    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, 'Appointment updated successfully!')
        return response
    
    def get_success_url(self):
        return reverse('appointments:appointment-detail', kwargs={'pk': self.object.pk})


class AppointmentDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Appointment
    template_name = 'appointments/appointment_confirm_delete.html'
    success_url = reverse_lazy('appointment-list')
    
    def test_func(self):
        appointment = self.get_object()
        return (
            self.request.user == appointment.client or 
            self.request.user.is_staff
        )
    
    def delete(self, request, *args, **kwargs):
        response = super().delete(request, *args, **kwargs)
        messages.success(request, 'Appointment cancelled successfully!')
        return response


@method_decorator(csrf_exempt, name='dispatch')
class AppointmentStatusUpdateView(LoginRequiredMixin, View):
    def post(self, request, *args, **kwargs):
        appointment = get_object_or_404(Appointment, pk=kwargs['pk'])
        status = request.POST.get('status')
        
        # Check permissions
        if not (request.user.is_staff or request.user == appointment.client):
            return HttpResponseForbidden("You don't have permission to update this appointment.")
        
        # Validate status
        valid_statuses = dict(Appointment.STATUS_CHOICES).keys()
        if status not in valid_statuses:
            return JsonResponse(
                {'error': 'Invalid status'}, 
                status=400
            )
        
        # Update status
        appointment.status = status
        if status == 'completed':
            appointment.completed_at = timezone.now()
        appointment.save()
        
        messages.success(request, f'Appointment marked as {status}.')
        
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'status': 'success',
                'status_display': appointment.get_status_display(),
                'status_class': appointment.status
            })
            
        return redirect('appointments:appointment-detail', pk=appointment.pk)


def appointment_cancel(request, pk):
    """
    View to cancel an appointment.
    """
    appointment = get_object_or_404(Appointment, pk=pk)
    
    # Check permissions - only staff or the client can cancel
    if not (request.user.is_staff or request.user == appointment.client):
        return HttpResponseForbidden("You don't have permission to cancel this appointment.")
    
    if request.method == 'POST':
        appointment.status = 'cancelled'
        appointment.save()
        
        messages.success(request, 'Appointment has been cancelled.')
        
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'status': 'success',
                'message': 'Appointment cancelled successfully.'
            })
        
        return redirect('appointments:appointment-detail', pk=appointment.pk)
    
    # For GET requests, show a confirmation page
    context = {
        'appointment': appointment,
        'title': 'Cancel Appointment',
    }
    return render(request, 'appointments/appointment_confirm_cancel.html', context)


def appointment_complete(request, pk):
    """
    View to mark an appointment as completed.
    """
    appointment = get_object_or_404(Appointment, pk=pk)
    
    # Only staff can mark appointments as completed
    if not request.user.is_staff:
        return HttpResponseForbidden("Only staff members can complete appointments.")
    
    if request.method == 'POST':
        appointment.status = 'completed'
        appointment.completed_at = timezone.now()
        appointment.save()
        
        messages.success(request, 'Appointment has been marked as completed.')
        
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'status': 'success',
                'message': 'Appointment marked as completed.'
            })
        
        return redirect('appointments:appointment-detail', pk=appointment.pk)
    
    # For GET requests, show a confirmation page
    context = {
        'appointment': appointment,
        'title': 'Complete Appointment',
    }
    return render(request, 'appointments/appointment_confirm_complete.html', context)


def add_appointment_note(request, pk):
    appointment = get_object_or_404(Appointment, pk=pk)
    
    # Check permissions
    if not (request.user == appointment.client or 
            request.user == appointment.staff.user or 
            request.user.is_staff):
        return HttpResponseForbidden("You don't have permission to add notes to this appointment.")
    
    if request.method == 'POST':
        form = AppointmentNoteForm(request.POST)
        if form.is_valid():
            note = form.save(commit=False)
            note.appointment = appointment
            note.author = request.user
            note.save()
            messages.success(request, 'Note added successfully!')
    
    return redirect('appointments:appointment-detail', pk=appointment.pk)


def delete_appointment_note(request, pk):
    note = get_object_or_404(AppointmentNote, pk=pk)
    
    # Check permissions
    if not (request.user == note.author or request.user.is_staff):
        return HttpResponseForbidden("You don't have permission to delete this note.")
    
    appointment_pk = note.appointment.pk
    note.delete()
    messages.success(request, 'Note deleted successfully!')
    
    return redirect('appointments:appointment-detail', pk=appointment_pk)


class CalendarView(LoginRequiredMixin, TemplateView):
    template_name = 'appointments/calendar.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        # Get filter parameters
        staff_id = self.request.GET.get('staff')
        service_id = self.request.GET.get('service')
        
        # Get staff and service for filters
        context['all_staff'] = StaffMember.objects.filter(is_active=True)
        context['all_services'] = Service.objects.filter(is_active=True)
        
        # Set selected filters
        context['selected_staff'] = int(staff_id) if staff_id and staff_id.isdigit() else ''
        context['selected_service'] = int(service_id) if service_id and service_id.isdigit() else ''
        
        return context


# Service Views
class ServiceListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
    model = Service
    template_name = 'appointments/service_list.html'
    context_object_name = 'services'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def get_queryset(self):
        return Service.objects.all().order_by('name')


class ServiceDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
    model = Service
    template_name = 'appointments/service_detail.html'
    context_object_name = 'service'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['appointments'] = self.object.appointments.all()
        return context


class ServiceCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
    model = Service
    form_class = ServiceForm
    template_name = 'appointments/service_form.html'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, 'Service created successfully!')
        return response
    
    def get_success_url(self):
        return reverse('appointments:service-list')


class ServiceUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Service
    form_class = ServiceForm
    template_name = 'appointments/service_form.html'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, 'Service updated successfully!')
        return response
    
    def get_success_url(self):
        return reverse('appointments:service-list')


class ServiceDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Service
    template_name = 'appointments/service_confirm_delete.html'
    success_url = reverse_lazy('appointments:service-list')
    
    def test_func(self):
        return self.request.user.is_staff
    
    def delete(self, request, *args, **kwargs):
        response = super().delete(request, *args, **kwargs)
        messages.success(request, 'Service deleted successfully!')
        return response


# Staff Member Views
class StaffListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
    model = StaffMember
    template_name = 'appointments/staff_list.html'
    context_object_name = 'staff_members'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def get_queryset(self):
        return StaffMember.objects.select_related('user').order_by('user__first_name', 'user__last_name')


class StaffDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
    model = StaffMember
    template_name = 'appointments/staff_detail.html'
    context_object_name = 'staff'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        staff = self.get_object()
        
        # Get upcoming appointments
        context['upcoming_appointments'] = staff.appointments.filter(
            start_time__gte=timezone.now(),
            status__in=['scheduled', 'confirmed']
        ).order_by('start_time')
        
        # Get completed appointments (last 10)
        context['recent_appointments'] = staff.appointments.filter(
            status='completed'
        ).order_by('-start_time')[:10]
        
        # Get services this staff member provides
        context['services'] = staff.services.all()
        
        # Get availability schedule if it exists
        if hasattr(staff, 'availability'):
            context['availability'] = staff.availability.all()
        else:
            context['availability'] = []
        
        return context


class StaffCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
    model = StaffMember
    form_class = StaffMemberForm
    template_name = 'appointments/staff_form.html'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, 'Staff member added successfully!')
        return response
    
    def get_success_url(self):
        return reverse('appointments:staff-list')


class StaffUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = StaffMember
    form_class = StaffMemberForm
    template_name = 'appointments/staff_form.html'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, 'Staff member updated successfully!')
        return response
    
    def get_success_url(self):
        return reverse('appointments:staff-list')


class StaffDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = StaffMember
    template_name = 'appointments/staff_confirm_delete.html'
    success_url = reverse_lazy('appointments:staff-list')
    
    def test_func(self):
        return self.request.user.is_staff
    
    def delete(self, request, *args, **kwargs):
        response = super().delete(request, *args, **kwargs)
        messages.success(request, 'Staff member removed successfully!')
        return response


# Business Hours Views
class BusinessHoursView(LoginRequiredMixin, UserPassesTestMixin, TemplateView):
    template_name = 'appointments/business_hours.html'
    
    def test_func(self):
        return self.request.user.is_staff
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        days = [
            'monday', 'tuesday', 'wednesday', 
            'thursday', 'friday', 'saturday', 'sunday'
        ]
        
        business_hours = {}
        for day in days:
            try:
                business_hours[day] = BusinessHours.objects.get(day=day)
            except BusinessHours.DoesNotExist:
                business_hours[day] = BusinessHours(day=day)
        
        context['business_hours'] = business_hours
        return context
    
    def post(self, request, *args, **kwargs):
        days = [
            'monday', 'tuesday', 'wednesday', 
            'thursday', 'friday', 'saturday', 'sunday'
        ]
        
        for day in days:
            is_open = request.POST.get(f'{day}_is_open') == 'on'
            open_time = request.POST.get(f'{day}_open_time')
            close_time = request.POST.get(f'{day}_close_time')
            
            try:
                business_hours = BusinessHours.objects.get(day=day)
            except BusinessHours.DoesNotExist:
                business_hours = BusinessHours(day=day)
            
            business_hours.is_open = is_open
            
            if is_open and open_time and close_time:
                business_hours.open_time = open_time
                business_hours.close_time = close_time
            
            business_hours.save()
        
        messages.success(request, 'Business hours updated successfully!')
        return redirect('business-hours')


# API Views
from rest_framework import generics, permissions
from .serializers import (
    AppointmentSerializer, ServiceSerializer, 
    StaffMemberSerializer, AvailableSlotsSerializer
)

class AppointmentListAPIView(generics.ListCreateAPIView):
    serializer_class = AppointmentSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        queryset = Appointment.objects.filter(
            Q(client=self.request.user) | Q(staff__user=self.request.user)
        )
        
        # Apply filters
        status = self.request.query_params.get('status', None)
        if status is not None:
            queryset = queryset.filter(status=status)
            
        staff_id = self.request.query_params.get('staff', None)
        if staff_id is not None:
            queryset = queryset.filter(staff_id=staff_id)
            
        service_id = self.request.query_params.get('service', None)
        if service_id is not None:
            queryset = queryset.filter(service_id=service_id)
            
        date_from = self.request.query_params.get('date_from', None)
        if date_from is not None:
            queryset = queryset.filter(start_time__date__gte=date_from)
            
        date_to = self.request.query_params.get('date_to', None)
        if date_to is not None:
            queryset = queryset.filter(start_time__date__lte=date_to)
        
        return queryset.order_by('start_time')
    
    def perform_create(self, serializer):
        serializer.save(client=self.request.user)


class AppointmentDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = AppointmentSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        return Appointment.objects.filter(
            Q(client=self.request.user) | Q(staff__user=self.request.user)
        )


class AvailableSlotsAPIView(generics.GenericAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = AvailableSlotsSerializer
    
    def get_queryset(self):
        return None  # Not used, but required for the view
    
    def get(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)
        
        date = serializer.validated_data.get('date')
        service_id = serializer.validated_data.get('service')
        staff_id = serializer.validated_data.get('staff')
        
        try:
            service = Service.objects.get(id=service_id, is_active=True)
            
            if staff_id:
                staff = StaffMember.objects.get(id=staff_id, is_active=True)
                staff_members = [staff]
            else:
                staff_members = StaffMember.objects.filter(
                    services=service, 
                    is_active=True
                )
            
            available_slots = []
            
            for staff_member in staff_members:
                slots = get_available_slots(
                    date=date,
                    service=service,
                    staff=staff_member,
                    duration=service.duration
                )
                
                available_slots.extend([{
                    'staff_id': staff_member.id,
                    'staff_name': str(staff_member),
                    'start_time': slot[0].strftime('%Y-%m-%dT%H:%M:%S'),
                    'end_time': slot[1].strftime('%Y-%m-%dT%H:%M:%S'),
                } for slot in slots])
            
            # Sort slots by start time
            available_slots.sort(key=lambda x: x['start_time'])
            
            return Response({
                'date': date,
                'service': service.name,
                'available_slots': available_slots
            })
            
        except (Service.DoesNotExist, StaffMember.DoesNotExist) as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )
        except Exception as e:
            return Response(
                {'error': 'An error occurred while fetching available slots.'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


class ServiceListAPIView(generics.ListAPIView):
    serializer_class = ServiceSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        return Service.objects.filter(is_active=True)


class StaffListAPIView(generics.ListAPIView):
    serializer_class = StaffMemberSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        service_id = self.request.query_params.get('service', None)
        
        queryset = StaffMember.objects.filter(is_active=True)
        
        if service_id is not None:
            queryset = queryset.filter(services__id=service_id)
            
        return queryset.distinct()
