"""
Views relating to a trip's itinerary management, & medical information.

Each official trip should have an itinerary completed by trip leaders.
That itinerary specifies who (if anybody) will be driving for the trip,
what the intended route will be, when to worry, and more.
"""
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.forms.utils import ErrorList
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import DetailView, ListView, UpdateView

import ws.utils.perms as perm_utils
from ws import forms, models, wimp
from ws.decorators import group_required
from ws.mixins import TripLeadersOnlyView
from ws.utils.dates import itinerary_available_at, local_date, local_now


class TripItineraryView(UpdateView, TripLeadersOnlyView):
    """ A hybrid view for creating/editing trip info for a given trip. """

    model = models.Trip
    context_object_name = 'trip'
    template_name = 'trips/itinerary.html'
    form_class = forms.TripInfoForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        trip = context['trip']
        context['itinerary_available_at'] = itinerary_available_at(trip.trip_date)
        context['info_form_editable'] = trip.info_editable
        context['waiting_to_open'] = local_now() < context['itinerary_available_at']
        return context

    def get_initial(self):
        self.trip = self.object  # Form instance will become object
        return {'trip': self.trip}

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['instance'] = self.trip.info
        return kwargs

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        signups = self.trip.signup_set.filter(on_trip=True)
        on_trip = Q(pk__in=self.trip.leaders.all()) | Q(signup__in=signups)
        participants = models.Participant.objects.filter(on_trip).distinct()
        has_car_info = participants.filter(car__isnull=False)
        form.fields['drivers'].queryset = has_car_info
        return form

    def form_valid(self, form):
        if not self.trip.info_editable:
            verb = "modified" if self.trip.info else "created"
            form.errors['__all__'] = ErrorList([f"Itinerary cannot be {verb}"])
            return self.form_invalid(form)
        self.trip.info = form.save()
        self.trip.save()
        return super().form_valid(form)

    def get_success_url(self):
        return reverse('view_trip', args=(self.trip.pk,))


class AllTripsMedicalView(ListView):
    model = models.Trip
    template_name = 'trips/all/medical.html'
    context_object_name = 'trips'

    def get_queryset(self):
        trips = super().get_queryset().order_by('trip_date')
        today = local_date()
        return trips.filter(trip_date__gte=today)

    def get_context_data(self, **kwargs):
        context_data = super().get_context_data(**kwargs)
        context_data['wimps'] = wimp.active_wimps()
        return context_data

    @method_decorator(group_required('WSC', 'WIMP'))
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)


class TripMedicalView(DetailView):
    model = models.Trip
    template_name = 'trips/medical.html'

    @staticmethod
    def _can_view(trip, request):
        """ Leaders, chairs, and a trip WIMP can view this page. """
        return (
            perm_utils.in_any_group(request.user, ['WIMP'])
            or (trip.wimp and request.participant == trip.wimp)
            or perm_utils.leader_on_trip(request.participant, trip, True)
            or perm_utils.chair_or_admin(request.user, trip.required_activity_enum())
        )

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        """ Only allow creator, leaders of the trip, WIMP, and chairs. """
        # The normal `dispatch()` will populate self.object
        normal_response = super().dispatch(request, *args, **kwargs)

        trip = self.object
        if not self._can_view(trip, request):
            return render(request, 'not_your_trip.html', {'trip': trip})
        return normal_response

    def get_context_data(self, **kwargs):
        """ Get a trip info form for display as readonly. """
        context_data = super().get_context_data(**kwargs)
        trip = self.object
        participant = self.request.participant
        context_data['is_trip_leader'] = perm_utils.leader_on_trip(participant, trip)

        return context_data


class ChairTripView(DetailView):
    """ Give a view of the trip intended to let chairs approve or not.

    Will show just the important details, like leaders, description, & itinerary.
    """

    model = models.Trip
    template_name = 'chair/trips/view.html'

    @property
    def activity(self):
        return self.kwargs['activity']

    def get_queryset(self):
        """ All trips of this activity type.

        For identifying only trips that need attention, callers
        should probably also filter on `trip_date` and `chair_approved`.

        By declining to filter here, this prevents a 404 on past trips.
        (In other words, a trip in the past that may or may not have been
        approved can still be viewed as it would have been for activity chairs)
        """
        return models.Trip.objects.filter(activity=self.activity)

    def get_other_trips(self):
        """ Get the trips that come before and after this trip & need approval. """
        this_trip = self.get_object()

        ordered_trips = iter(
            self.get_queryset().filter(
                chair_approved=False, trip_date__gte=local_date()
            )
        )

        prev_trip = None
        for trip in ordered_trips:
            if trip.pk == this_trip.pk:
                try:
                    next_trip = next(ordered_trips)
                except StopIteration:
                    next_trip = None
                break
            prev_trip = trip
        else:
            return None, None  # (Could be the last unapproved trip)
        return prev_trip, next_trip

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['activity'] = self.activity

        # Provide buttons for quick navigation between upcoming trips needing approval
        context['prev_trip'], context['next_trip'] = self.get_other_trips()
        return context

    def post(self, request, *args, **kwargs):
        """ Mark the trip approved and move to the next one, if any. """
        trip = self.get_object()
        _, next_trip = self.get_other_trips()  # Do this before saving trip
        trip.chair_approved = True
        trip.save()
        if next_trip:
            return redirect(
                reverse('view_trip_for_approval', args=(self.activity, next_trip.id))
            )
        return redirect(reverse('manage_trips', args=(self.activity,)))

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        trip = self.get_object()
        if not perm_utils.is_chair(request.user, trip.required_activity_enum()):
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)