import logging
from collections import OrderedDict

from django import forms
from django.forms import ModelForm

from web.models.worker_study_role import WORKER_STAFF
from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, APPOINTMENT_TYPES_FIELD_POSITION
from web.models import Appointment, Worker, AppointmentTypeLink, AppointmentType, Provenance
from web.views.notifications import get_filter_locations
from django.db.models.query import QuerySet

logger = logging.getLogger(__name__)


class AppointmentForm(ModelForm):
    datetime_when = forms.DateTimeField(label='Appointment on',
                                        widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS)
                                        )

    def __init__(self, *args, **kwargs):
        super(AppointmentForm, self).__init__(*args, **kwargs)
        self.fields['worker_assigned'].queryset = Worker.get_workers_by_worker_type(WORKER_STAFF).filter(
            locations__in=get_filter_locations(self.user)).distinct().order_by('first_name', 'last_name')

    def save_changes(self):
        for change in self.changes:
            if change.modified_table_id is None:
                change.modified_table_id = self.instance.id
            change.save()

    def register_changes(self):
        self.changes = []
        for field in self.changed_data:
            new_value = self.cleaned_data[field]
            if isinstance(new_value, QuerySet):
                new_human_values = '; '.join([str(element) for element in new_value]) 
                new_value = ','.join([str(element.id) for element in new_value]) #overwrite variable
                #old value
                if self.instance.id: #update instance
                    previous_value = getattr(self.instance, field).all()
                    old_human_values = '; '.join([str(element) for element in previous_value]) 
                    previous_value = ','.join([str(element.id) for element in previous_value]) #overwrite variable
                else: #new instance
                    old_human_values = ''
                    previous_value = ''
                #description
                description = '{} changed from "{}" to "{}"'.format(field, old_human_values, new_human_values)
            else:
                if self.instance.id: #update instance
                    previous_value = str(getattr(self.instance, field))
                else:
                    previous_value = ''
                new_value = str(self.cleaned_data[field])
                description = '{} changed from "{}" to "{}"'.format(field, previous_value, new_value)
  
            p = Provenance(modified_table = Appointment._meta.db_table,
                            modified_table_id = self.instance.id,
                            modification_author=self.user,
                            previous_value = previous_value,
                            new_value = new_value,
                            modification_description = description,
                            modified_field = field,
                            )
            self.changes.append(p)

class AppointmentDetailForm(AppointmentForm):
    class Meta:
        model = Appointment
        fields = '__all__'


class AppointmentEditForm(AppointmentForm):
    class Meta:
        model = Appointment
        fields = '__all__'
        exclude = ['appointment_types']

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        if user is None:
            raise TypeError("User not defined")
        self.user = Worker.get_by_user(user)
        if self.user is None:
            raise TypeError("Worker not defined for: " + user.username)
        super(AppointmentEditForm, self).__init__(*args, **kwargs)
        if 'instance' in kwargs:
            initial_appointment_types = AppointmentTypeLink.objects.filter(appointment=kwargs['instance']).values_list(
                'appointment_type', flat=True)
        else:
            initial_appointment_types = []
        fields = OrderedDict()
        for i, tuple in enumerate(self.fields.items()):
            key, value = tuple
            fields[key] = value
            if i == APPOINTMENT_TYPES_FIELD_POSITION:
                fields['appointment_types'] = forms.ModelMultipleChoiceField(required=False,
                                                                             widget=forms.CheckboxSelectMultiple,
                                                                             queryset=AppointmentType.objects.all(),
                                                                             initial=initial_appointment_types)
        self.fields = fields
        self.fields['location'].queryset = get_filter_locations(self.user)

    def clean_location(self):
        location = self.cleaned_data['location']
        if self.user.locations.filter(id=location.id).count() == 0:
            self.add_error('location', "You cannot create appointment for this location")
        else:
            return location

    def clean(self):
        self.register_changes() #right before instance is changed

    def save(self, commit=True):

        appointment = super(AppointmentEditForm, self).save(commit)
        # if appointment date change, remove appointment_type links
        if 'datetime_when' in self.changed_data:
            AppointmentTypeLink.objects.filter(appointment=appointment).delete()
            appointment_type_links = []
        else:
            appointment_type_links = AppointmentTypeLink.objects.filter(appointment=appointment).all()
        appointment_types_from_links = []
        appointment_types = self.cleaned_data['appointment_types']
        for appointment_type_link in appointment_type_links:
            if appointment_type_link.appointment_type not in appointment_types:
                # we delete appointment links for appointments types that have been removed
                appointment_type_link.delete()
            else:
                appointment_types_from_links.append(appointment_type_link.appointment_type)
        for appointment_type in appointment_types:
            if appointment_type not in appointment_types_from_links:
                # we create new appointment links for appointments types that have been added
                appointment_type_link = AppointmentTypeLink(appointment=appointment,
                                                            appointment_type=appointment_type)
                appointment_type_link.save()
        
        self.save_changes()
        return appointment


class AppointmentAddForm(AppointmentForm):
    class Meta:
        model = Appointment
        exclude = ['status', 'appointment_types']

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        if user is None:
            raise TypeError("User not defined")
        self.user = Worker.get_by_user(user)
        if self.user is None:
            raise TypeError("Worker not defined for: " + user.username)

        super(AppointmentAddForm, self).__init__(*args, **kwargs)
        fields = OrderedDict()
        for i, tuple in enumerate(self.fields.items()):
            key, value = tuple
            fields[key] = value
            if i == APPOINTMENT_TYPES_FIELD_POSITION:
                fields['appointment_types'] = forms.ModelMultipleChoiceField(required=False,
                                                                             widget=forms.CheckboxSelectMultiple,
                                                                             queryset=AppointmentType.objects.all(),
                                                                             )
        fields['worker_assigned'].widget.attrs = {'class': 'search_worker_availability'}
        fields['datetime_when'].widget.attrs = {'class': 'start_date', 'placeholder': 'yyyy-mm-dd HH:MM',
        'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}'}
        fields['length'].widget.attrs = {'class': 'appointment_duration'}

        self.fields = fields
        self.fields['location'].queryset = get_filter_locations(self.user)

    def clean(self):
        self.register_changes() #right before instance is changed

    def clean_location(self):
        location = self.cleaned_data['location']
        if self.user.locations.filter(id=location.id).count() == 0:
            self.add_error('location', "You cannot create appointment for this location")
        else:
            return location

    def save(self, commit=True):
        appointment = super(AppointmentAddForm, self).save(commit)
        appointment_types = self.cleaned_data['appointment_types']
        for appointment_type in appointment_types:
            appointment_type_link = AppointmentTypeLink(appointment=appointment, appointment_type=appointment_type)
            appointment_type_link.save()
        self.save_changes()
        return appointment