from datetime import datetime

from django.db.models import Count
from collections import OrderedDict

from staff.lib.decorators import available_by_center_token

from staff.person.models import Staff, Organization, StaffCar, StaffPhone, MemorialProfile
from staff.departments.models import Department
from staff.map.models import Office, City, Country, Table

from staff.apicenter.utils import _DATETIME_FORMATS, convert_edu_status_backwards
from staff.lib.models.departments_chain import DepartmentsChain
from staff.keys.models import SSHKey

from . import ApiView

_def_staff_fields = (
    'id',
    'login',
    'login_ld',
    'first_name',
    'last_name',
    'work_email',
    'work_phone',
    'position'
)
_backward_related_fields = {
    'ssh_keys': {'model': SSHKey,
                 'fields': ('key', 'description')},
    'phones': {'model': StaffPhone,
               'fields': ('number', 'type', 'description')},
    'cars': {'model': StaffCar,
             'fields': ('model', 'plate')},
    'memorial_profile': {'model': MemorialProfile,
                         'fields': ('status_block_html',
                                    'death_date')}

}
_full_staff_fields = ApiView.full_fields(Staff, {
    'department': Department,
    'office': Office,
    'office__city': City,
    'office__city__country': Country,
    'table': Table,
    'organization': Organization,
})


@available_by_center_token
class UsersView(ApiView):

    entities = ('users', 'user')

    def prepare_data(self, *args, **kwargs):
        requested_fields = self.get_requested_fields(
            var_name='fields', default=_def_staff_fields)
        required_fields = ['id']
        if 'departments' in requested_fields:
            required_fields.append('department__id')
        if 'edu_status' in requested_fields:
            required_fields.append('edu_direction')

        fields = self.get_fields(
            model=Staff,
            var_name='fields',
            default_fields=_def_staff_fields,
            full_fields=_full_staff_fields,
            required_fields=required_fields
        )
        qs = Staff.objects.values(*fields).order_by('modified_at')
        last_modified = self._get_date(field_name='last_modified')
        include_dismissed = self._get_bool(field_name='include_dismissed')
        wo_photo = self._get_bool(field_name='without_photo', default=False)

        if last_modified:
            qs = qs.filter(modified_at__gte=last_modified)
        if not include_dismissed:
            qs = qs.filter(is_dismissed=False)
        if wo_photo:
            qs = qs.annotate(img_count=Count('images')).filter(img_count=0)
        staff_list = list(qs)

        requested_backward_related_fields = (set(requested_fields) &
                                             set(_backward_related_fields))
        for field in requested_backward_related_fields:
            self._append_backward_related_models(staff_list, field)

        if 'departments' in requested_fields:
            self._append_departments_chain(staff_list)

        if 'edu_status' in requested_fields:
            self._convert_edu_statuses(staff_list)

        if requested_fields != ['__all__']:
            redundant_fields = set(required_fields) - set(requested_fields)
            self._remove_fields(staff_list, redundant_fields)

            self._sort_fields(staff_list, requested_fields)

        self.data = staff_list

    def _get_date(self, field_name):
        """
        Метод преобразует строку, переданную в get-параметре `field_name` в
        объект datetime.datetime.
        """
        date_str = self.request.GET.get(field_name)
        if date_str is None:
            return

        date_val = None
        for _format in _DATETIME_FORMATS:
            try:
                date_val = datetime.strptime(date_str, _format)
            except ValueError:
                continue
            else:
                break
        if date_val is None:
            raise Exception('Bad date format for %s' % field_name)
        return date_val

    def _get_bool(self, field_name, default=True):
        """
        Метод преобразует строку, переданнуй в get-параметре `field_name` в
        объект bool. Допустимые значения 0 и 1.
        """
        bool_str = self.request.GET.get(field_name)
        if bool_str is None:
            return default
        elif bool_str == '0':
            return False
        elif bool_str == '1':
            return True
        else:
            raise Exception('%s must be 0 or 1' % field_name)

    def _append_backward_related_models(self, staff_list, field):
        """
        К каждому словарю с данными Staff-пользователя в списке добавляется
        ключ `field` со списком словарей с данными из связанной (backward
        relation) модели
        """
        model = _backward_related_fields[field]['model']
        all_objects = model.objects.filter(intranet_status=1).iterator()

        staff_objects = {}
        for obj in all_objects:
            obj_data = {}
            for obj_attr in _backward_related_fields[field]['fields']:
                if hasattr(obj, 'get_'+obj_attr+'_display'):
                    obj_data[obj_attr] = getattr(obj,
                                                 'get_'+obj_attr+'_display')()
                else:
                    obj_data[obj_attr] = getattr(obj, obj_attr)
            try:
                staff_objects.setdefault(obj.staff_id, []).append(obj_data)
            except AttributeError:
                staff_objects.setdefault(obj.person_id, []).append(obj_data)

        for staff in staff_list:
            staff[field] = staff_objects.get(staff['id'])

    def _append_departments_chain(self, staff_list):
        """
        Добавить список департаментов в staff_list
        """
        chain = DepartmentsChain()
        for staff in staff_list:
            department_id = staff['department__id']
            if department_id is not None:
                departments_chain = chain.get_chain_as_list(
                    department_id=department_id)
            else:
                departments_chain = []
            staff['departments'] = list(map(str, departments_chain))

    def _convert_edu_statuses(self, staff_list):
        """Преобразуем значения образования для обратной совместимости"""
        for staff in staff_list:
            staff['edu_status'] = convert_edu_status_backwards(
                edu_status=staff['edu_status'],
                edu_direction=staff['edu_direction'])

    def _remove_fields(self, staff_list, field_list):
        """
        Метод удаляет поля, перечисленные в `field_list` из `staff_list`
        """
        if field_list:
            for staff in staff_list:
                for field in field_list:
                    del staff[field]

    def _sort_fields(self, staff_list, order_list):
        """
        Метод заменяет словари в списке на сортированные словари в порядке,
        установленном в списке `order_list`
        """
        for index, staff in enumerate(staff_list):
            key_val_pairs = []
            for _field in order_list:
                if _field in staff:
                    key_val_pairs.append((_field, staff[_field]))
            staff_list[index] = OrderedDict(key_val_pairs)
