from django.utils import timezone
from django.db.models import Q
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.views import APIView

from .models import AppointmentSlot, Appointment, SlotTemplate
from .serializers import (
    AppointmentSlotSerializer,
    AppointmentSlotAdminSerializer,
    AppointmentBookSerializer,
    AppointmentSerializer,
    AppointmentAdminSerializer,
    SlotTemplateSerializer,
)
from .emails import send_appointment_confirmation
from .services import generate_slots_for_weeks


class SlotTemplateViewSet(viewsets.ModelViewSet):
    """Admin : CRUD templates de créneaux."""
    queryset = SlotTemplate.objects.all()
    serializer_class = SlotTemplateSerializer
    permission_classes = [permissions.IsAdminUser]


class AppointmentSlotViewSet(viewsets.ReadOnlyModelViewSet):
    """
    - GET /slots/ : liste des créneaux (admin : tous ; public : non utilisé sans auth).
    - GET /slots/available/ : créneaux disponibles (AllowAny pour la page publique).
    """
    serializer_class = AppointmentSlotSerializer

    def get_permissions(self):
        if self.action == "available":
            return [permissions.AllowAny()]
        return [permissions.IsAuthenticated()]

    def get_queryset(self):
        qs = AppointmentSlot.objects.all().filter(date__gte=timezone.localdate()).order_by("date", "start_time")
        if not self.request.user.is_authenticated or not self.request.user.is_staff:
            return qs
        return qs

    def get_serializer_class(self):
        if self.request.user.is_authenticated and self.request.user.is_staff:
            return AppointmentSlotAdminSerializer
        return AppointmentSlotSerializer

    @action(detail=False, methods=["get"], url_path="available")
    def available(self, request):
        """Créneaux disponibles (date >= aujourd'hui, pas encore réservés ou annulés)."""
        qs = (
            AppointmentSlot.objects.filter(date__gte=timezone.localdate())
            .order_by("date", "start_time")
        )
        available_ids = [s.id for s in qs if s.is_available]
        qs = qs.filter(id__in=available_ids)
        serializer = AppointmentSlotSerializer(qs, many=True)
        return Response(serializer.data)


class AppointmentViewSet(viewsets.ModelViewSet):
    """
    - POST /appointments/book/ : réserver un créneau (public : email obligatoire).
    - GET /appointments/ : liste (admin : tous ; user : les siens).
    - PATCH /appointments/:id/cancel/ : annuler.
    """
    queryset = Appointment.objects.all().select_related("slot", "user")

    def get_serializer_class(self):
        if self.request.user.is_authenticated and self.request.user.is_staff:
            return AppointmentAdminSerializer
        return AppointmentSerializer

    def get_permissions(self):
        if self.action == "book":
            return [permissions.AllowAny()]
        return [permissions.IsAuthenticated()]

    def get_queryset(self):
        qs = Appointment.objects.all().select_related("slot", "user")
        if self.request.user.is_staff:
            return qs
        return qs.filter(Q(user=self.request.user) | Q(guest_email__iexact=self.request.user.email))

    @action(detail=False, methods=["post"], url_path="book")
    def book(self, request):
        """Réserver un créneau. Tout le monde peut réserver ; email obligatoire."""
        ser = AppointmentBookSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        slot_id = ser.validated_data["slot_id"]
        guest_email = ser.validated_data["guest_email"].strip()
        guest_name = (ser.validated_data.get("guest_name") or "").strip()

        try:
            slot = AppointmentSlot.objects.get(pk=slot_id)
        except AppointmentSlot.DoesNotExist:
            return Response({"detail": "Créneau introuvable."}, status=status.HTTP_404_NOT_FOUND)

        if not slot.is_available:
            return Response({"detail": "Ce créneau n'est plus disponible."}, status=status.HTTP_400_BAD_REQUEST)

        if slot.date < timezone.localdate():
            return Response({"detail": "Ce créneau est passé."}, status=status.HTTP_400_BAD_REQUEST)

        user = request.user if request.user.is_authenticated else None
        appointment = Appointment.objects.create(
            slot=slot,
            user=user,
            guest_email=guest_email,
            guest_name=guest_name,
            status=Appointment.Status.PENDING,
        )
        sent = send_appointment_confirmation(appointment)
        if sent:
            appointment.email_sent_at = timezone.now()
            appointment.save(update_fields=["email_sent_at"])

        serializer = AppointmentSerializer(appointment)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=["post"], url_path="cancel")
    def cancel(self, request, pk=None):
        """Annuler un rendez-vous (owner ou admin)."""
        appointment = self.get_object()
        if not request.user.is_staff:
            if request.user.is_authenticated:
                if appointment.user_id != request.user.id and appointment.guest_email.lower() != request.user.email.lower():
                    return Response({"detail": "Non autorisé."}, status=status.HTTP_403_FORBIDDEN)
            else:
                return Response({"detail": "Authentification requise pour annuler."}, status=status.HTTP_403_FORBIDDEN)
        appointment.status = Appointment.Status.CANCELLED
        appointment.save(update_fields=["status", "updated_at"])
        serializer = self.get_serializer(appointment)
        return Response(serializer.data)


class GenerateSlotsAPIView(APIView):
    """Admin : générer les créneaux pour les N prochaines semaines."""
    permission_classes = [permissions.IsAdminUser]

    def post(self, request):
        num_weeks = int(request.data.get("weeks", 4))
        num_weeks = max(1, min(12, num_weeks))
        count = generate_slots_for_weeks(num_weeks=num_weeks)
        return Response({"created": count, "message": f"{count} créneau(x) créé(s)."})
