import sform

from itertools import chain

from staff.departments.models import DepartmentStaff, DepartmentRoles
from staff.person.models import StaffCar, FAMILY_STATUS, GENDER, EMPLOYMENT

from staff.lib.utils.qs_values import localize, extract_related
from staff.lib.models.departments_chain import get_departments_tree
from staff.lib.models.mptt import filter_by_heirarchy
from staff.person.models import Bicycle


class ExportForm(sform.SForm):
    data_format = sform.ChoiceField(
        choices=(('csv', 'csv'), ('xlsx', 'xlsx')),
        default='csv',
    )

    lang = sform.ChoiceField(
        choices=(('ru', 'ru'), ('en', 'en')),
        default='ru',
    )

    login = sform.BooleanField(default=True)
    name = sform.BooleanField(default=True)
    work_email = sform.BooleanField(default=True)
    home_email = sform.BooleanField(default=False)
    position = sform.BooleanField(default=True)
    department_chain = sform.BooleanField(default=True)
    department = sform.BooleanField(default=False)
    work_phone = sform.BooleanField(default=False)
    chief = sform.BooleanField(default=False)
    hr_partner = sform.BooleanField(default=False)
    duties = sform.BooleanField(default=False)
    office = sform.BooleanField(default=False)
    city = sform.BooleanField(default=False)
    country = sform.BooleanField(default=False)
    organization = sform.BooleanField(default=False)
    employment = sform.BooleanField(default=False)
    join_at = sform.BooleanField(default=False)
    birthday = sform.BooleanField(default=False)
    external_login = sform.BooleanField(default=False)
    cars = sform.BooleanField(default=False)
    bicycles = sform.BooleanField(default=False)
    gender = sform.BooleanField(default=False)
    family_status = sform.BooleanField(default=False)
    mobile_phone_model = sform.BooleanField(default=False)
    tshirt_size = sform.BooleanField(default=False)
    children = sform.BooleanField(default=False)
    edu_place = sform.BooleanField(default=False)
    table = sform.BooleanField(default=False)
    floor = sform.BooleanField(default=False)


class PersonsExport(object):
    @classmethod
    def meta(cls):
        return ExportForm().as_dict()

    def __init__(self, filter_context, url, form):
        self.context = filter_context
        self.url = url
        self.form = form
        self.department_chains = None
        self.hr_partners_and_chiefs = None
        self.cars = None
        self.bicycles = None

    locale_fields = (
        'department__name',
        'position',
        'office__name',
        'office__city__name',
        'office__city__country__name',
        'organization__name',
        'last_name',
        'first_name',
        'duties',
        'table__floor__name',
    )

    def _get_name_fields(self):
        name_fields = 'last_name', 'first_name', 'middle_name'
        if self.form.cleaned_data['lang'] == 'en':
            name_fields = name_fields[:-1]
        return name_fields

    def get_employment(self, data):
        if data['employment']:
            return [str(EMPLOYMENT[data['employment']])]
        else:
            return ['']

    def get_family_status(self, data):
        if data['family_status']:
            return [str(FAMILY_STATUS[data['family_status']])]
        else:
            return ['']

    def get_gender(self, data):
        if data['gender']:
            return [str(GENDER[data['gender']])]
        else:
            return ['']

    def get_work_phone(self, data):
        return [str(data['work_phone']) if data['work_phone'] else '']

    name_fields = 'last_name', 'first_name', 'middle_name',

    def get_name(self, data):
        return [' '.join(data[f] for f in self._get_name_fields())]

    department_fields = 'department__name', 'department__url',
    department_captions = 'Department Name', 'Department Url'

    def get_department(self, data):
        return [data['department__name'], data['department__url']]

    office_fields = 'office__name',
    city_fields = 'office__city__name',
    country_fields = 'office__city__country__name',
    organization_fields = 'organization__name',
    external_login_fields = 'login_passport',

    def get_join_at(self, data):
        return [data['join_at'].isoformat() if data['join_at'] else '']

    def get_birthday(self, data):
        return [data['birthday'].isoformat() if data['birthday'] else '']

    department_chain_fields = 'department_id',

    def department_chain_extra(self, persons):
        if self.department_chains is not None:
            return
        dep_ids = (
            [p['department_id'] for p in persons]
            if self.context.filter_id
            else None
        )
        department_chains = get_departments_tree(
            departments=dep_ids,
            fields=['name', 'name_en', 'id'],
        )
        self.department_chains = {
            k: [localize(d) for d in v] for k, v in department_chains.items()
        }

    def get_department_chain(self, data):
        dep_chain = self.department_chains.get(data['department_id'], [])
        return [' → '.join(d['name'] for d in dep_chain)]

    def chiefs_and_hr_partners_extra(self, persons):
        if self.hr_partners_and_chiefs is not None:
            return
        self.department_chain_extra(persons)

        q = DepartmentStaff.objects.filter(
            role_id__in=(DepartmentRoles.CHIEF.value, DepartmentRoles.HR_PARTNER.value)
        )

        if self.context.filter_id:
            departments_ids = set()
            for dep_chain in self.department_chains.values():
                for dep in dep_chain:
                    departments_ids.add(dep['id'])
            q = q.filter(department_id__in=departments_ids)

        departments = q.values(
            'department_id',
            'staff__login',
            'staff__last_name',
            'staff__first_name',
            'staff__middle_name',
            'staff__last_name_en',
            'staff__first_name_en',
            'role',
        )

        self.hr_partners_and_chiefs = {}

        for v in departments:
            id_ = v.pop('department_id')
            chiefs = self.hr_partners_and_chiefs.setdefault(id_, [])
            chief = localize(extract_related(v, 'staff'))
            chief['role'] = v['role']
            chiefs.append(chief)

    chief_fields = 'department_id', 'login'
    chief_captions = 'Chief Login', 'Chief Name'

    def chief_extra(self, persons):
        self.chiefs_and_hr_partners_extra(persons)

    def get_chief(self, person):
        chiefs = self._find_persons_in_role_for_person(person, DepartmentRoles.CHIEF.value)

        if not chiefs:
            return ['', '']

        person_chief = chiefs[0]
        name_fields = self._get_name_fields()
        return [
            person_chief['login'],
            ' '.join(person_chief[f] for f in name_fields),
        ]

    hr_partner_fields = 'department_id', 'login'
    hr_partner_captions = 'HR Partner Login', 'HR Partner Name'

    def hr_partner_extra(self, persons):
        self.chiefs_and_hr_partners_extra(persons)

    def _find_persons_in_role_for_person(self, person, role):
        ancestors = self.department_chains.get(person['department_id'], [])

        for dep in reversed(ancestors):
            hr_or_chiefs = self.hr_partners_and_chiefs.get(dep['id'])

            hr_or_chiefs = [
                hr_or_chief for hr_or_chief in hr_or_chiefs or []
                if hr_or_chief['role'] == role and hr_or_chief['login'] != person['login']
            ]

            if hr_or_chiefs:
                return hr_or_chiefs

        return None

    def get_hr_partner(self, person):
        hr_partners = self._find_persons_in_role_for_person(person, DepartmentRoles.HR_PARTNER.value)

        if not hr_partners:
            return ['', '']

        logins = ','.join(h['login'] for h in hr_partners)

        name_fields = self._get_name_fields()
        names = ','.join(
            ' '.join(hr_partner[field] for field in name_fields)
            for hr_partner in hr_partners
        )
        return [logins, names]

    cars_fields = 'id',

    def cars_extra(self, persons):
        if self.cars is not None:
            return

        car_qs = StaffCar.objects.filter(intranet_status=1).values('staff_id', 'plate', 'model')
        if self.context.filter_id:
            car_qs = car_qs.filter(staff_id__in=[p['id'] for p in persons])

        self.cars = {}
        for car in car_qs.order_by('position'):
            cars = self.cars.setdefault(car['staff_id'], [])
            cars.append(car)

    def get_cars(self, data):
        return [', '.join(
            '%(plate)s (%(model)s)' % car
            for car in self.cars.get(data['id'], [])
        )]

    bicycles_fields = 'id',

    def bicycles_extra(self, persons):
        if self.bicycles is not None:
            return

        bicycle_qs = Bicycle.objects.values('owner_id', 'plate', 'description')
        if self.context.filter_id:
            bicycle_qs = bicycle_qs.filter(owner_id__in=[p['id'] for p in persons])

        self.bicycles = {}
        for bicycle in bicycle_qs.order_by('position'):
            bicycles = self.bicycles.setdefault(bicycle['owner_id'], [])
            bicycles.append(bicycle)

    def get_bicycles(self, data):
        return [', '.join(
            '%(plate)s (%(description)s)' % bicycle
            for bicycle in self.bicycles.get(data['id'], [])
        )]

    floor_fields = 'table__floor__name',

    def _get_active_fields(self):

        def _default_get(field):
            return lambda data: [data[field]]

        active_fields = [
            f for f in self.form.fields
            if self.form.cleaned_data.get(f) and f not in ('lang', 'data_format')
        ]

        result = []

        for field in active_fields:
            try:
                captions = getattr(self, field + '_captions')
            except AttributeError:
                captions = [
                    ' '.join(part.capitalize() for part in field.split('_'))
                ]

            try:
                fields = getattr(self, field + '_fields')
            except AttributeError:
                fields = [field]

            try:
                get = getattr(self, 'get_' + field)
            except AttributeError:
                get = _default_get(fields[0])

            try:
                extra = getattr(self, field + '_extra')
            except AttributeError:
                extra = None

            result.append({
                'name': field,
                'fields': fields,
                'get': get,
                'extra': extra,
                'captions': captions,
            })

        return result

    def _filter_by_url(self, qs):
        if self.url:
            dep = self.context.get_base_dep_qs().get(url=self.url)
            qs = filter_by_heirarchy(
                qs,
                mptt_objects=[dep],
                by_children=True,
                include_self=True,
                filter_prefix='department__',
            )
        return qs

    def export(self):
        active_fields = self._get_active_fields()

        query_fields = ['id']
        for field in active_fields:
            query_fields += field['fields']

        for field in query_fields:
            if field in self.locale_fields:
                query_fields.append(field + '_en')

        persons_qs = self.context.get_person_obj_qs()
        persons_qs = self._filter_by_url(persons_qs)
        persons_qs = persons_qs.order_by(
            'department__tree_id',
            'department__lft',
            '-is_big_boss',
        )
        persons_qs = persons_qs.values(*set(query_fields))

        for field in active_fields:
            if field['extra']:
                field['extra'](persons_qs)

        result = []

        result.append(
            list(chain.from_iterable(f['captions'] for f in active_fields))
        )
        for person in persons_qs:
            person = localize(person)
            row_data = []

            for field in active_fields:
                row_data += field['get'](person)

            row_data = ['' if d is None else d for d in row_data]

            result.append(row_data)

        return result
