from datetime import datetime
from typing import Dict, List, AnyStr, Any, Generator, Iterable

from staff.lib import waffle

from django.conf import settings
from django.core.cache import cache
from django.db.models import Q

from staff.person.models import Staff, AFFILIATION

from staff.lib.models.departments_chain import get_departments_tree
from staff.lib.utils.qs_values import localize, extract_related

from staff.jabber import get_status_bulk
from staff.whistlah.utils import get_saved_activities
from staff.gap.controllers.staff_utils import GapsByPersons
from staff.gap.workflows import WORKFLOWS_COLORS


CACHE_DIRECTIONS_PREFIX = 'CARD_DIRECTION_%s'
CACHE_DIRECTIONS_TTL = 60 * 60 * 24  # сутки


def get_persons_with_related(query: Dict[str, Any]) -> Generator[Dict[AnyStr, Any], None, None]:
    fields = (
        'id',
        'login',
        'affiliation',

        'first_name',
        'first_name_en',

        'last_name',
        'last_name_en',

        'position',
        'position_en',

        'work_email',
        'work_phone',

        'mobile_phone',

        'is_dismissed',

        'gender',

        'office__id',
        'office__name',
        'office__name_en',

        'department__id',
        'department__url',
        'department__code',
        'department__parent_id',
        'department__short_name',
        'department__short_name_en',
        'department__name',
        'department__name_en',
        'department__bg_color',
        'department__fg_color',

        'room__id',
        'room__name',
        'room__name_en',

        'table_id',

        'memorial_profile__intranet_status',
    )
    persons = Staff.objects.filter(**query).values(*fields)

    if waffle.switch_is_active('rkn_mode'):
        persons = persons.filter(Q(is_dismissed=False) | Q(memorial_profile__intranet_status=1))

    return (localize(dict(p)) for p in persons)


def get_gaps(person_ids: Iterable[int], observer: Staff) -> Dict[int, Dict[str, Any]]:
    tigran_is_viewer = observer.login == settings.LOGIN_TIGRAN

    gaps = (
        GapsByPersons(datetime.now(), observer_tz=observer.tz)
        .sort_by_rank()
        .only_one()
        .get_by_ids(list(person_ids))
    )

    return {
        person_id: get_one_gap_data(gap)
        for person_id, gap in gaps.items()
        if gap['person_login'] != settings.LOGIN_TIGRAN or tigran_is_viewer
    }


def get_one_gap_data(gap: Dict[str, Any]) -> Dict[str, Any]:
    gap_data = {
        'name': gap['workflow'],
        'left_edge': gap['left_edge'],
        'right_edge': gap['right_edge'],
        'date_from': gap['date_from'],
        'date_to': gap['date_to'],
        'full_day': gap['full_day'],
        'work_in_absence': gap['work_in_absence'],
        'description': gap['comment'],
        'id': gap['id'],
        'staff_id': gap['person_id'],
        'color': WORKFLOWS_COLORS.get(gap['workflow'], '#000000'),
    }

    if gap['workflow'] == 'illness':
        gap_data['is_covid'] = gap.get('is_covid', False)

    return gap_data


def get_directions(departments_ids):
    cached = cache.get_many([
        CACHE_DIRECTIONS_PREFIX % dep_id for dep_id in departments_ids
    ])

    ids_to_select = [
        dep_id for dep_id in departments_ids
        if CACHE_DIRECTIONS_PREFIX % dep_id not in cached
    ]

    deps_tree = get_departments_tree(
        departments=ids_to_select,
        fields=[
            'id',
            'code',
            'short_name',
            'short_name_en',
            'name',
            'name_en',
            'kind_id',
            'bg_color',
            'fg_color',
        ],
    )

    directions = {}

    for dep_id in departments_ids:
        cache_key = CACHE_DIRECTIONS_PREFIX % dep_id

        if dep_id in deps_tree:
            direction = [
                localize(d) for d in deps_tree[dep_id]
                if d['kind_id'] == settings.DIS_DIRECTION_KIND_ID
            ]

            if direction:
                directions[dep_id] = direction[0]
                cache.set(cache_key, direction[0], CACHE_DIRECTIONS_TTL)

        elif cache_key in cached:
            directions[dep_id] = cached[cache_key]

    return directions


def get_last_activity_with_office_name(actual_logins):
    last_activity = get_saved_activities(actual_logins)

    for value in last_activity.values():
        value['office'] = localize(value)['name']

    return last_activity


def split_email_and_jabber(person):
    person['custom_work_email'] = person['work_email']
    if person['affiliation'] != AFFILIATION.YAMONEY:
        person['work_email'] = person['login'] + '@yandex-team.ru'


def get_joined_persons_data(query: Dict[str, Any], observer: Staff) -> List[Dict[AnyStr, Any]]:
    persons_gen = get_persons_with_related(query)

    persons_ids = set()
    departments_ids = set()
    actual_logins = set()
    persons = []

    for person in persons_gen:
        person['department'] = extract_related(person, 'department')
        person['office'] = extract_related(person, 'office')
        person['room'] = extract_related(person, 'room')
        persons.append(person)

        persons_ids.add(person['id'])
        departments_ids.add(person['department']['id'])
        actual_logins.add(person['login'])

    directions = get_directions(departments_ids)
    jabber_statuses = get_status_bulk(actual_logins)
    gaps = get_gaps(persons_ids, observer)
    last_activity = get_last_activity_with_office_name(actual_logins)

    for person in persons:
        split_email_and_jabber(person)
        person['direction'] = directions.get(person['department']['id'])
        person['gap'] = gaps.get(person['id'])
        person['jabber_status'] = jabber_statuses.get(person['login'])
        person['last_activity'] = last_activity.get(person['login'])

    return persons
