from datetime import datetime

from phonenumbers import (
    format_number,
    PhoneNumberFormat,
    parse,
    NumberParseException,
)

from staff.lib.exceptions import ErrorWithStatusCode

from staff.person.models import Staff, StaffPhone, PHONE_TYPES, PHONE_KIND, PHONE_PROTOCOLS
from staff.person.controllers import PersonCtl
from staff.person_profile.errors import log_does_not_exist_staff_id

import logging
logger = logging.getLogger('person_profile.controllers.phones')


class PhonesError(ErrorWithStatusCode):
    pass


class PhonesCtl(object):
    phones_format = PhoneNumberFormat.INTERNATIONAL

    def __init__(self, target_login, permissions_ctl):
        self.login = target_login
        self.permissions_props = permissions_ctl.properties
        self.person_id = self.permissions_props.get_target_data(self.login)['id']

    def _format_number(self, number_data):
        try:
            number_data['number'] = format_number(
                parse(number_data['number']),
                self.phones_format
            )
        except (NumberParseException, UnicodeDecodeError):
            pass
        return number_data

    def get_phones(self, for_view=True, **filters):
        phones_qs = (
            StaffPhone.objects
            .values(
                'id',
                'number',
                'kind',
                'description',
                'protocol',
                'for_digital_sign',
            )
            .filter(staff__login=self.login, intranet_status=1, **filters)
            .order_by('position')
        )

        can_view_emergency = bool(
            self.permissions_props.is_superuser
            or
            self.permissions_props.is_owner(self.login)
            or
            self.permissions_props.get_is_chief(self.login)
            or
            'django_intranet_stuff.can_view_emergency_phone' in self.permissions_props.permissions
        )

        if not can_view_emergency:
            phones_qs = phones_qs.exclude(kind=PHONE_KIND.EMERGENCY)

        can_view_hidden = bool(
            self.permissions_props.is_superuser
            or
            self.permissions_props.is_owner(self.login)
        )

        if not can_view_hidden:
            phones_qs = phones_qs.exclude(kind=PHONE_KIND.HIDDEN)

        return [self._format_number(phone_data) for phone_data in phones_qs]

    def update_phones(self, phones_data):
        phones = StaffPhone.objects.values_list(
            'id',
            'for_digital_sign',
            'number',
        ).filter(staff_id=self.person_id, intranet_status=1)
        id_to_for_digital_sign = {}
        id_to_number = {}
        can_be_deleted = set()
        for phone_id, for_digital_sign, number in phones:
            id_to_for_digital_sign[phone_id] = for_digital_sign
            id_to_number[phone_id] = number
            if not for_digital_sign:
                can_be_deleted.add(phone_id)

        now = datetime.now()
        for position, form_data in enumerate(phones_data):
            phone_data = {
                'number': form_data['number'],
                'kind': form_data['kind'],
                'description': form_data['description'],
                'protocol': form_data['protocol'],
                'staff_id': self.person_id,
                'position': position,
                'modified_at': now,
                'created_at': now,
            }

            if phone_data['protocol'] == PHONE_PROTOCOLS.VOICE:
                phone_data['type'] = PHONE_TYPES.HOME
            else:
                phone_data['type'] = PHONE_TYPES.MOBILE

            phone_id = form_data.get('id')
            is_editing = phone_id is not None

            if is_editing:
                phone_data['id'] = phone_id
                can_be_deleted.discard(phone_id)
                if id_to_for_digital_sign[phone_id]:
                    # forbidden to update number if it is for digital sign
                    phone_data['number'] = id_to_number[phone_id]
                    phone_data['for_digital_sign'] = True

            try:
                StaffPhone(**phone_data).save(
                    force_insert=not is_editing,
                    force_update=is_editing,
                )
            except Exception as e:
                logger.exception('Error while trying to save StaffPhone')
                raise PhonesError(e)

        if can_be_deleted:
            for phone in StaffPhone.objects.filter(id__in=can_be_deleted):
                try:
                    phone.intranet_status = 0
                    phone.save(update_fields=['intranet_status'])
                except StaffPhone.DoesNotExist as e:
                    logger.exception('There are no such phone: %s', phone)
                    raise PhonesError(e)

        with log_does_not_exist_staff_id(
            logger=logger,
            message_params=[self.person_id],
            raise_e=PhonesError
        ):
            person = Staff.objects.get(id=self.person_id)
            PersonCtl(person).save(author_user=person.user)
