from datetime import datetime, date
from uuid import uuid4
import re
from itertools import chain

from django.conf import settings
from django.db.models import (
    Model as DjModel,
    OneToOneField,
    CharField,
    TextField,
    ForeignKey,
)

from staff.person.models import Staff
from staff.map.models import Country

from staff.lib.models.base import I18nModel, DisSoftDeletedModel
from staff.lib.utils.ordered_choices import StaffChoices
from staff.person.dis_staff_services import get_chiefs_role_bulk

from staff.lib.utils.dict import invert_multivaluedict
from staff.lib.models.roles_chain import get_grouped_hrbp_by_persons

from staff.trip_questionary.controller.approvers import TripApprovers, ConfApprovers

from .mongo_models import Collection, CollectionObject


EVENT_TYPE = StaffChoices(
    TRIP=('trip', 'trip'),
    TRIP_CONF=('trip_conf', 'trip and conference'),
    CONF=('conf', 'conference'),
)


class TripQuestionaryException(Exception):
    pass


class TripQuestionary(CollectionObject):
    _approvers = None
    _chiefs = None
    _is_foreign = None
    _recipients = None
    _hr_partners = None
    uuid = None

    technical_fields = (
        '_id',
        'uuid',
        'is_locked',
        'is_ui_locked',
        'last_sync',
        'is_new',
        'author',
        'creation_time',
        ('employee_list', 'gap'),
        ('employee_list', 'trip_issue'),
        ('employee_list', 'conf_issue'),
        'is_creation_notification_sent',
        'trip_issue',
        'conf_issue',
    )

    def init(self, init_data=None, **kwargs):

        if self.is_new:
            self.uuid = kwargs.get('uuid', self._make_uuid())
            init_data['uuid'] = self.uuid
            init_data['is_locked'] = False
            init_data['is_ui_locked'] = False
            init_data['last_sync'] = datetime.now()
            init_data['author'] = kwargs['author']

        else:
            self.uuid = init_data['uuid']

        init_data['is_new'] = self.is_new

    @staticmethod
    def _make_uuid():
        return str(uuid4())

    def get_spec(self):
        return {'uuid': self.uuid}

    def get_dehydrated_data(self):

        def dehydrate(data):
            if isinstance(data, DjModel):
                return str(data.pk)
            elif type(data) == date:
                return datetime(*data.timetuple()[:3])
            elif isinstance(data, (list, map)):
                return [dehydrate(v) for v in data]
            elif isinstance(data, dict):
                s_data = {}
                for k, v in data.items():
                    s_data[dehydrate(k)] = dehydrate(v)
                return s_data
            return data

        dehydrated_data = dict(dehydrate(self.data))

        return dehydrated_data

    def get_hydrated_data(self, data):

        def replace_value(node, field, func):
            value = node.get(field)
            if value:
                node[field] = func(value)

        def datetime_2_date(dt):
            return dt.date() if isinstance(dt, datetime) else dt

        def id_2_object_staff(s_id):
            return Staff.objects.get(pk=int(s_id))

        def id_2_object_component(s_id):
            return CityAsStartrekComponent.objects.get(pk=int(s_id))

        replace_value(data, 'trip_date_from', datetime_2_date)
        replace_value(data, 'trip_date_to', datetime_2_date)
        replace_value(data, 'event_date_from', datetime_2_date)
        replace_value(data, 'event_date_to', datetime_2_date)
        replace_value(data, 'author', id_2_object_staff)
        replace_value(data, 'st_component', id_2_object_component)

        employee_list = data.get('employee_list', [])

        for employee in employee_list:
            replace_value(employee, 'employee', id_2_object_staff)
            replace_value(employee, 'departure_date', datetime_2_date)
            replace_value(employee, 'return_date', datetime_2_date)
            replace_value(employee, 'mobile_date_from', datetime_2_date)
            replace_value(employee, 'mobile_date_to', datetime_2_date)
            replace_value(employee, 'st_component', id_2_object_component)

        city_list = data.get('city_list', [])

        for city in city_list:
            replace_value(city, 'departure_date', datetime_2_date)
            replace_value(city, 'arrival_date', datetime_2_date)
            replace_value(city, 'return_departure_date', datetime_2_date)

        hydrated = {}
        notificated_chiefs = data.setdefault('notificated_chiefs', hydrated)
        for chief_id, people in notificated_chiefs.items():
            hydrated_people = [id_2_object_staff(emp) for emp in people]
            hydrated[str(chief_id)] = hydrated_people
        data['notificated_chiefs'] = hydrated

        return data

    def has_issues(self):
        trip_issue = self.data.get('trip_issue', False)
        conf_issue = self.data.get('conf_issue', False)
        return bool(trip_issue or conf_issue)

    @property
    def lock_name(self):
        return 'trip_' + self.uuid

    def pre_save(self):
        if self.is_new:
            self.data['creation_time'] = datetime.now()

    def pre_delete(self):
        if self.has_issues():
            raise TripQuestionaryException('Cannot delete TripQuestionary')

    def _init_chiefs_approvers_recipients(self):
        staff_list = [e['employee'] for e in self.data['employee_list']]

        chiefs_role = get_chiefs_role_bulk(staff_list, level_gte=1)

        self._chiefs = dict(
            (staff, roles[0].staff if roles else None)
            for staff, roles in chiefs_role.items()
        )

        # Подтверждающие
        approvers_kwargs = dict(
            author=self.data['author'],
            staff_list=staff_list,
            foreign=self.is_foreign(),
            chiefs_role=chiefs_role,
        )
        if self.data['event_type'] == EVENT_TYPE.TRIP:
            self._approvers = TripApprovers(**approvers_kwargs).get()
        else:
            approvers_kwargs['hr_partners'] = self.get_hr_partners()
            self._approvers = ConfApprovers(**approvers_kwargs).get()

        # Получающие доступ + уведомлённые по email
        self._recipients = get_recipients(
            self.data['author'],
            staff_list,
            chiefs_role,
        )

        # Получающие доступ без уведомления по email
        self._accessors = get_accessors(
            self.data['author'],
            staff_list,
            chiefs_role,
        )

    def get_notified_accessors(self):
        if not self._recipients:
            self._init_chiefs_approvers_recipients()
        return invert_multivaluedict(self._recipients)

    def get_accessors(self):
        if not self._accessors:
            self._init_chiefs_approvers_recipients()
        return self._accessors

    def get_approvers(self):
        if self._approvers is None:
            self._init_chiefs_approvers_recipients()

        return self._approvers

    def get_approver(self, employee):
        approvers = self.get_approvers()
        return approvers[employee.id]

    def get_recipients(self):
        if self._recipients is None:
            self._init_chiefs_approvers_recipients()

        return self._recipients

    def get_employee_recipients(self, employee):
        recipients = self.get_recipients()
        return recipients[employee]

    def get_hr_partners(self):
        staff_list = [e['employee'] for e in self.data['employee_list']]

        if self._hr_partners is None:
            self._hr_partners = get_hr_partners(staff_list)
        return self._hr_partners

    def get_employee_hr_partners(self, employee, default=None):
        default = default or []
        hr_partners = self.get_hr_partners()
        return hr_partners.get(employee.id, default)

    def get_employee_accessors(self, employee):
        accessors = self.get_accessors()
        return accessors[employee]

    def get_chiefs(self):
        if self._chiefs is None:
            self._init_chiefs_approvers_recipients()

        return self._chiefs

    def get_employee_chief(self, employee, default=None):
        return self.get_chiefs().get(employee, default)

    def get_country_list(self):
        country_list = [
            c['country']
            for c in self.data.get('city_list', [])
            if not c['is_return_route']
        ]
        return country_list

    def is_foreign(self):

        if self.data['event_type'] == EVENT_TYPE.CONF:
            self._is_foreign = False

        if self._is_foreign is None:
            country_list = self.get_country_list()
            sng = SNGCountry.get_all_spellings()

            countries = (
                c.name if isinstance(c, Country) else c
                for c in country_list
            )
            countries = (re.sub(r'\s', '', c.lower()) for c in countries)
            countries = ('__sng__' if c in sng else c for c in countries)
            countries = set(countries) - {'__sng__'}

            self._is_foreign = len(countries) > 0

        return self._is_foreign


class TripQuestionaryCollection(Collection):
    collection_name = 'trip_questionary'
    model = TripQuestionary


def get_recipients(author, staff_list, chiefs_role):
    immediate_chief_index = 0

    result = {}

    for person in staff_list:
        result[person] = []

        roles = chiefs_role.get(person, [])
        chief_recipient = [
            r for r in roles
            if r.department.kind.id in settings.TOP_DEPARTMENT_TYPES
        ]
        if roles:
            recipients = {roles[immediate_chief_index].staff}
            try:
                recipients.add(chief_recipient[0].staff)
            except IndexError:
                pass
            recipients.discard(author)

            # В trip_conf в очереди TRAVEL утверждающего нет, а доступ ему нужен
            # recipients.discard(approvers[person])
            result[person] = list(recipients)

    return result


def get_accessors(author, staff_list, chiefs_role):
    result = {}

    for person in staff_list:
        recipients = set([role.staff for role in chiefs_role.get(person, [])])
        recipients.discard(author)
        result[person] = list(recipients)

    return result


def get_hr_partners(staff_list):
    def person_as_dict(person):
        return {
            'id': person.id,
            'department': {
                'id': person.department.id,
                'tree_id': person.department.tree_id,
                'lft': person.department.lft,
                'rght': person.department.rght,
            }
        }

    hr_partners = get_grouped_hrbp_by_persons(
        person_list=[person_as_dict(p) for p in staff_list],
        fields=['login'],
    )
    return hr_partners


class CityAsStartrekComponent(DjModel):
    city = OneToOneField('django_intranet_stuff.City',
                         verbose_name='Город')

    # ST Component id
    component = CharField(max_length=50,
                          verbose_name='Стартречный компонент')
    trip_money_email = CharField(max_length=512,
                                 verbose_name='trip-money@')
    hr_travel_email = CharField(max_length=512,
                                verbose_name='hr-travel@')
    hr_mobile_email = CharField(max_length=512,
                                verbose_name='hr-mobile@')

    lang = CharField(max_length=2,
                     verbose_name='Язык')
    # Рассылка это рассылка
    mailing_list = CharField(max_length=512,
                             verbose_name='Рассылка')

    money_person = ForeignKey(Staff, verbose_name='Бухгалтер ex trip money', related_name='+')
    hr_person = ForeignKey(Staff, verbose_name='HR ex hr travel', related_name='+')


class DepartmentAsStartrekComponent(DjModel):
    department = OneToOneField('django_intranet_stuff.Department',
                               verbose_name='Департамент')
    # ST Component id
    component = CharField(max_length=50,
                          verbose_name='Стартречный компонент')

    def __str__(self):
        params = vars(self)
        # OneToOneField doesn't work as I expected
        params.update(department=self.department)
        return '{department} => {component}'.format(**params)


class SNGCountry(DjModel):
    name = CharField(max_length=255, verbose_name='Название')
    spellings = TextField(verbose_name='Варианты написания')

    def iter_spellings(self):
        for spelling in self.spellings.split():
            stripped = spelling.strip()
            if stripped:
                yield stripped

    def get_spellings(self):
        return list(self.iter_spellings())

    @classmethod
    def get_all_spellings(cls):
        return list(chain.from_iterable(c.get_spellings() for c in cls.objects.all()))

    # class Meta:
    #     verbose_name = 'Страна СНГ'
    #     verbose_name_plural = 'Страны СНГ'


class TripPurpose(I18nModel, DisSoftDeletedModel):
    name = CharField(max_length=128, verbose_name='Название')
    name_en = CharField(max_length=128, verbose_name='Англ. название')

    description = TextField(verbose_name='Описание')

    def __str__(self):
        return self.i_name
