import logging

from functools import partial
from pytz import timezone
from datetime import datetime, timedelta

from django.conf import settings

from staff.lib.mongodb import mongo

from staff.gap.controllers.gap import GapCtl, GapQueryBuilder
from staff.gap.workflows.choices import GAP_STATES as GS, LIMITED_STATES


logger = logging.getLogger(__name__)

EMPTY_DELTA = timedelta()

WORKFLOW_RANK = {
    'vacation': 100,
    'paid_day_off': 95,
    'illness': 90,
    'absence': 80,
    'remote_work': 75,
    'office_work': 74,
    'trip': 70,
    'conference_trip': 60,
    'conference': 55,
    'learning': 50,
    'maternity': 40,
    'duty': 30,
}


def _convert_tz(dt, dt_tz, tz):
    if dt_tz == tz:
        return dt

    return dt_tz.localize(dt).astimezone(tz).replace(tzinfo=None)


class GapsByPersons(object):
    fields = [
        'id',
        'workflow',
        'date_from',
        'date_to',
        'comment',
        'person_id',
        'person_login',
        'state',
        'full_day',
        'work_in_absence',
        'is_covid',
        'service_slug',
        'service_name',
        'shift_id',
        'role_on_duty',
    ]

    def __init__(self, date_from=None, date_to=None, observer_tz=settings.TIME_ZONE, can_see_all=False):
        self._result = {}
        self._key_field = None
        self._only_one = False
        self._only_keys = False
        self._sort_by = None
        self.observer_tz = timezone(observer_tz)
        self.utc_tz = timezone('UTC')

        as_utc = partial(
            _convert_tz,
            dt_tz=timezone(settings.TIME_ZONE),
            tz=self.utc_tz,
        )
        self.date_from = as_utc(date_from or date_to or datetime.now())
        self.date_to = as_utc(date_to or date_from or datetime.now())

        self.query_builder = (
            GapQueryBuilder()
            .dates_not_strict(self.date_from, self.date_to)
        )

        if can_see_all:
            self.query_builder.op_ne('state', GS.CANCELED)
        else:
            self.query_builder.workflows_states(LIMITED_STATES)

    def get_by_logins(self, person_logins):
        assert self._key_field is None, 'Only one "get" method can be call'
        self._key_field = 'person_login'
        self.query_builder.person_logins(person_logins)
        return self._execute()

    def get_by_ids(self, person_ids):
        assert self._key_field is None, 'Only one "get" method can be call'
        self._key_field = 'person_id'
        self.query_builder.person_ids(person_ids)
        return self._execute()

    def get_all(self, key_field):
        assert self._key_field is None, 'Only one "get" method can be call'
        self._key_field = key_field
        return self._execute()

    def filter_by_workflows(self, workflows):
        self.query_builder.workflows(workflows)
        return self

    def filter_by_work_in_absence(self, work_in_absence):
        self.query_builder.work_in_absence(work_in_absence)
        return self

    def only_one(self):
        self._only_one = True
        return self

    def only_keys(self):
        self._only_keys = True
        self._result = set()
        return self

    def sort_by_rank(self):
        self._sort_by = 'rank'
        return self

    def sort_by_date(self):
        self._sort_by = 'date'
        return self

    def _execute(self):
        gaps = GapCtl().find_gaps(query=self.query_builder.query(), fields=self.fields)

        if self._sort_by:
            gaps = sorted(gaps, **self._get_sort_kwargs())

        add_method = self._add_key if self._only_keys else self._add_gaps

        as_observer_tz = partial(
            _convert_tz,
            dt_tz=self.utc_tz,
            tz=self.observer_tz,
        )

        for gap in gaps:
            if gap['full_day']:
                gap['date_to'] = gap['date_to'] - timedelta(1)
            else:
                gap['date_from'] = as_observer_tz(gap['date_from'])
                gap['date_to'] = as_observer_tz(gap['date_to'])

            # Подделка под старые даты. Надо будет выкинуть.
            gap['left_edge'] = gap['date_from'].date()
            gap['right_edge'] = max(gap['left_edge'], gap['date_to'].date())
            add_method(gap)

        if self._only_keys:
            return self._result

        if self._only_one:
            self._to_leave_one()

        return self._result

    def _add_gaps(self, gap):
        person_gaps = self._result.setdefault(gap[self._key_field], [])
        person_gaps.append(gap)

    def _add_key(self, gap):
        self._result.add(gap[self._key_field])

    def _get_sort_kwargs(self):
        return {
            'date': {'key': lambda pg: pg['date_from']},
            'rank': {'key': self._sort_key},
        }.get(self._sort_by)

    @staticmethod
    def _sort_key(gap):
        full_day_weight = 41000000
        weight = (gap['date_to'] - gap['date_from']).seconds
        weight += WORKFLOW_RANK[gap['workflow']]
        if gap['full_day']:
            weight += full_day_weight
        return -weight

    def _to_leave_one(self):
        for key, person_gaps in self._result.items():
            self._result[key] = person_gaps[0]


def collect_forms_person_ids(gaps):
    q_or = [{'uuid': gap['form_key']} for gap in gaps if 'form_key' in gap]
    if not q_or:
        return {}

    result = {}

    try:
        collection = mongo.db.get_collection('trip_questionary')
        trips = collection.find({'$or': q_or})
    except KeyError:
        logger.exception('Error looking for trips')
        return {}

    for trip in trips:
        ids = result.setdefault(trip['uuid'], set())
        if 'author' in trip:
            ids.add(int(trip['author']))

        for employee in trip['employee_list']:
            ids.add(int(employee['employee']))

    return result
