from staff.achievery.models import Achievement, GivenAchievement
from staff.achievery.serializers.icon import IconSerializer
from staff.person.models import Staff


class Mock(object):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)


def serialize_achievement(obj, context, fields):
    res = {}
    groups = context['groups']
    icons = context['icons']
    localize = context['localize']
    role_registry = context['role_registry']

    if 'given_count' in fields:
        res['given_count'] = context['given_counts'].get(obj['id'], 0)

    for f in ('id', 'category', 'created_at', 'is_active'):
        _maybe_insert_field(obj, res, f, fields=fields)

    for f in ('description', 'description_html', 'description_short', 'title'):
        if f in fields:
            _maybe_insert_lang_pair(
                obj, res, f, f, localize,
                fields=fields.get(f) or ACHIEVEMENT_FIELDS['title']
            )

    if ('service_name' in obj or 'service_name_en' in obj) and 'service' in fields:
        service = {}
        _maybe_insert_lang_pair(
            obj, service, 'service_name', 'name', localize,
            fields=(fields.get('service') and
                    fields.get('service').get('name') or
                    ACHIEVEMENT_FIELDS['service']['name'])
        )
        res.update(service=service)

    if 'owner_group' in obj and 'owner_group' in fields:
        group = _serialize_group(
            groups[obj['owner_group']], context,
            fields=fields.get('owner_group') or ACHIEVEMENT_FIELDS['owner_group']
        )
        res['owner_group'] = group

    if 'icon_big' in fields:
        big = icons.get((obj['id'], -1, True))
        res['icon_big'] = _serialize_icon(
            big, context,
            fields=fields.get('icon_big') or ACHIEVEMENT_FIELDS['icon_big']
        ) if big else None

    if 'icon_small' in fields:
        small = icons.get((obj['id'], -1, False))
        res['icon_small'] = _serialize_icon(
            small, context,
            fields=fields.get('icon_small') or ACHIEVEMENT_FIELDS['icon_small']
        ) if small else None

    if 'is_counter' in fields:
        res['is_counter'] = any(  # FIXME: Multilevel cache?
            (obj['id'], level, big) in icons
            for level in range(20)  # Waaaagh!!!
            for big in (True, False)
        )

    if 'roles' in fields:
        instance = Mock(
            __class__=Achievement,
            id=obj['id'],
            is_active=obj['is_active'],
        )
        res['roles'] = role_registry.get_bool_map(instance)

    return res


def serialize_given(obj, context, fields, is_internal_observer=True):
    res = {}
    achievements = context['achievements']
    persons = context['persons']
    icons = context['icons']
    events = context['events_by_givens']
    role_registry = context['role_registry']

    for f in ('id', 'comment', 'comment_html', 'is_hidden', 'is_active',
              'level', 'revision', 'slot'):
        _maybe_insert_field(obj, res, f, fields=fields)

    if 'achievement_id' in obj and 'achievement' in fields:
        res['achievement'] = serialize_achievement(
            achievements[obj['achievement_id']], context,
            fields=fields.get('achievement') or GIVEN_FIELDS['achievement']
        )

    if 'person_id' in obj and 'person' in fields:
        res['person'] = _serialize_person(
            persons[obj['person_id']], context,
            fields=fields.get('person') or GIVEN_FIELDS['person']
        )

    if 'icon_big' in fields:
        big = icons.get((obj['achievement_id'], obj['level'], True))
        res['icon_big'] = _serialize_icon(
            big, context,
            fields=fields.get('icon_big') or GIVEN_FIELDS['icon_big']
        ) if big else None

    if 'icon_small' in fields:
        small = icons.get((obj['achievement_id'], obj['level'], False))
        res['icon_small'] = _serialize_icon(
            small, context,
            fields=fields.get('icon_small') or GIVEN_FIELDS['icon_small']
        ) if small else None

    if 'events' in fields:
        res['events'] = [
            _serialize_event(
                event, context,
                fields=fields.get('events') or GIVEN_FIELDS['events'],
                is_internal_observer=is_internal_observer,
            )
            for event in events.get(obj['id'], ())
        ]

    if 'last_event_at' in fields:
        last = events.get(obj['id'], (None,))[0]
        res['last_event_at'] = last['created_at'] if last else last

    if 'roles' in fields:
        instance = Mock(
            __class__=GivenAchievement,
            id=obj['id'],
            is_active=obj['is_active'],
            is_hidden=obj['is_hidden'],
            person_id=obj.get('person', obj['person_id']),
            achievement_id=obj.get('achievement', obj['achievement_id']),
        )
        res['roles'] = role_registry.get_bool_map(instance)

    return res


def _serialize_event(obj, context, fields, is_internal_observer=True):
    res = {}
    persons = context['persons']

    for f in ('comment', 'is_hidden', 'is_active', 'level', 'revision', 'slot'):
        _maybe_insert_field(obj, res, f, fields=fields)

    _maybe_insert_field(obj, res, 'created_at', 'happened_at', fields=fields)

    if 'initiator' in fields and is_internal_observer:
        res['initiator'] = _serialize_person(
            persons[obj['initiator_id']], context,
            fields=fields.get('initiator') or GIVEN_FIELDS['initiator']
        )

    return res


def _serialize_group(obj, context, fields):
    res = {}
    persons = context['persons_by_groups']

    for f in 'id', 'name', 'url':
        _maybe_insert_field(obj, res, f, fields=fields)

    if 'members' in fields:
        res['members'] = [
            _serialize_person(
                person, context,
                fields=fields.get('members') or GIVEN_FIELDS['members']
            )
            for person in persons[obj['id']]
        ]

    return res


def _serialize_person(obj, context, fields):
    res = {}
    localize = context['localize']

    for f in ('id', 'login', 'uid', 'is_dismissed'):
        _maybe_insert_field(obj, res, f, fields=fields)

    fields_to_check = ('first_name', 'first_name_en', 'last_name', 'last_name_en')

    if any(f in obj for f in fields_to_check) and 'name' in fields:
        name = {}
        if 'first_name' in obj or 'first_name_en' in obj and 'first' in fields['name']:
            _maybe_insert_lang_pair(
                obj, name, 'first_name', 'first', localize,
                fields=fields['name']['first'] or PERSON_FIELDS['name']['first']
            )
        if 'last_name' in obj or 'last_name_en' in obj and 'last' in fields['name']:
            _maybe_insert_lang_pair(
                obj, name, 'last_name', 'last', localize,
                fields=fields['name']['last'] or PERSON_FIELDS['name']['last']
            )
        res['name'] = name

    return res


def _serialize_icon(obj, context, fields):
    res = {}

    for f in ('id', 'level', 'is_big'):
        _maybe_insert_field(obj, res, f, fields=fields)

    if 'url' in fields:
        res['url'] = IconSerializer.get_icon_url(Mock(**obj))

    return res


def _maybe_insert_field(dict_from, dict_to, field_from, field_to=None,
                        fields=None):
    assert fields
    if field_to is None:
        field_to = field_from

    if field_from in dict_from and field_to in fields:
        dict_to.update({field_to: dict_from[field_from]})


def _maybe_insert_lang_pair(dict_from, dict_to, field_from, field_to,
                            localize=None, fields=None):
    assert fields
    lp = {}

    _maybe_insert_field(dict_from, lp, field_from, 'ru', fields=fields)
    _maybe_insert_field(dict_from, lp, field_from + '_en', 'en', fields=fields)

    if localize:
        localized_field_from = field_from + '_en' if localize == 'en' else field_from
        _maybe_insert_field(
            dict_from, lp, localized_field_from, 'localized', fields=['localized']
        )

    dict_to.update({field_to: lp})


def has_access_to_achievements_info(observer: Staff, target_login: str) -> bool:
    """
    :raises ObjectDoesNotExist: если человека с нужным логином нет
    """
    target = Staff.objects.get(login=target_login)
    return (
        target_login == observer.login
        or observer.has_access_to_department_profiles(target.department_id)
    )


LANG_FIELDS = {
    'ru': None,
    'en': None,
}


PERSON_FIELDS = {
    'id': None,
    'login': None,
    'name': {
        'first': LANG_FIELDS,
        'last': LANG_FIELDS,
    },
    'uid': None,
    'is_dismissed': None,
}


ICON_FIELDS = {
    'id': None,
    'level': None,
    'is_big': None,
    'url': None,
}


ACHIEVEMENT_FIELDS = {
    'id': None,
    'category': None,
    'created_at': None,
    'description': LANG_FIELDS,
    'description_html': LANG_FIELDS,
    'description_short': LANG_FIELDS,
    'title': LANG_FIELDS,
    'owner_group': {
        'id': None,
        'name': None,
        'url': None,
        'members': PERSON_FIELDS
    },
    'service': {
        'name': LANG_FIELDS,
    },
    'icon_big': ICON_FIELDS,
    'icon_small': ICON_FIELDS,
    'is_active': None,
    'is_counter': None,
    'roles': None,
    'given_count': None,
}


GIVEN_FIELDS = {
    'id': None,
    'achievement': ACHIEVEMENT_FIELDS,
    'person': PERSON_FIELDS,
    'comment': None,
    'comment_html': None,
    'is_hidden': None,
    'is_active': None,
    'level': None,
    'revision': None,
    'slot': None,
    'icon_big': ICON_FIELDS,
    'icon_small': ICON_FIELDS,
    'events': {
        'comment': None,
        'is_hidden': None,
        'is_active': None,
        'level': None,
        'revision': None,
        'slot': None,
        'initiator': PERSON_FIELDS,
        'happened_at': None,
    },
    'last_event_at': None,
    'roles': None,
}
