from datetime import datetime
from staff.lib import waffle
from django.http import HttpResponseBadRequest

from staff.departments.models import DepartmentStaff, DepartmentRoles
from staff.lib.models.mptt import filter_by_heirarchy
from staff.person.models import Staff

from staff.gap.controllers.gap import GapQueryBuilder, GapCtl
from staff.gap.controllers.utils import get_chief, get_universal_person
from staff.gap.exceptions import WrongWorkflowStateError
from staff.gap.workflows.base_workflow import BaseWorkflow
from staff.gap.workflows.decorators import allowed_states
from staff.gap.workflows.choices import GAP_STATES as GS
from staff.gap.workflows.utils import confirm_by_chief_hash_action


class AbsenceWorkflow(BaseWorkflow):
    editable_fields = ['full_day'] + BaseWorkflow.editable_fields

    workflow = 'absence'
    ui_key = 'gap.workflow.absence'
    gap_type = 'absence'
    verbose_name = 'Отсутствие'
    verbose_name_en = 'Absence'
    color = '#ffc136'

    template_context = {
        'new_gap_head_ru': 'Отсутствие создано',
        'new_gap_head_en': 'Absence was created',
        'edit_gap_head_ru': 'Отсутствие изменено',
        'edit_gap_head_en': 'Absence was edited',
        'cancel_gap_head_ru': 'Отсутствие отменено',
        'cancel_gap_head_en': 'Absence was canceled',
    }

    LONG_ABSENCE_HOURS = 5.0
    LONG_ABSENCES_WO_APPROVAL = 3

    def _chiefs_followers(self):
        person_qs = (
            Staff.objects
            .filter(login=self.gap['person_login'])
            .values('department__tree_id', 'department__rght', 'department__lft')
        )

        roles_qs = (
            DepartmentStaff.objects
            .filter(role_id=DepartmentRoles.CHIEF.value)
            .exclude(staff__login='volozh')
            .order_by('-department__level')
            .values_list('staff__login', flat=True)
        )

        chiefs_chain = filter_by_heirarchy(
            roles_qs,
            person_qs,
            by_children=False,
            include_self=True,
            prefix='department__',
            filter_prefix='department__'
        )

        if chiefs_chain and chiefs_chain[0] == self.gap['person_login']:
            chiefs_chain = chiefs_chain[1:]

        return list(chiefs_chain)[:2]

    def _get_approver(self):
        if not self.is_long_absence_and_needs_approval():
            return get_chief(self.gap['person_login'])

        chief_followers = self._chiefs_followers()
        approver_login = chief_followers[-1] if chief_followers else None
        return get_universal_person(person_login=approver_login) if approver_login else None

    def _append_chief_for_notify(self, approver):
        if not self.is_long_absence_and_needs_approval():
            return

        if not approver:
            return

        chief = get_chief(self.gap['person_login'], ['work_email'])

        if not chief:
            return

        if approver['id'] != chief['id']:
            self.gap['to_notify'].append(chief['work_email'])

    def new_gap(self, data):
        self._create_self_gap(data)

        self._set_duration()
        self._set_need_approval()
        self._update_confirm_action_hash()
        self._update()

        approver = self._get_approver()

        if self.is_long_absence_and_needs_approval():
            self.create_ticket()
            self._auto_confirm_if_applicable()

        self._append_chief_for_notify(approver)
        self._new_gap_email(approver=approver)

        return self.gap

    def create_ticket(self):
        chief_followers = self._chiefs_followers()
        hrbp_followers = self.hrpb_logins

        self.new_issue('new_long_absence', append_followers=chief_followers + hrbp_followers)

    def _issue_edit_comment(self, was_long, become_long, was_confirmed, updated):
        if was_long and become_long:
            if was_confirmed:
                self.issue_comment('reopen_long_absence', forbidden_statuses=['cancelled'], gap_diff=updated)
            else:
                self.issue_comment('edit_long_absence', forbidden_statuses=['cancelled'], gap_diff=updated)

            return

        become_short = not become_long
        if was_long and become_short:
            self.issue_comment('cancel_long_absence', forbidden_statuses=['cancelled'])
            return

        was_short = not was_long
        if was_short and become_long:
            self.create_ticket()
            return

    @allowed_states([GS.NEW, GS.CONFIRMED])
    def edit_gap(self, data):
        was_long = self.is_long_absence_and_needs_approval()

        updated = self._update_editable(data, self.editable_fields)
        if not updated:
            return

        was_confirmed = self.gap['state'] == GS.CONFIRMED
        if was_confirmed and ('date_from' in updated or 'work_in_absence' in updated):
            self._set_state(GS.NEW)
            self.gap['confirmed_by_id'] = None

        self._set_duration()
        self._set_need_approval()
        self._update_confirm_action_hash()
        self._update()

        become_long_and_needs_approval = self.is_long_absence_and_needs_approval()
        self._issue_edit_comment(was_long, become_long_and_needs_approval, was_confirmed, updated)

        self._new_to_notify(updated)

        approver = self._get_approver()
        self._append_chief_for_notify(approver)

        self._edit_gap_email(updated, approver=approver)

        if become_long_and_needs_approval:
            self._auto_confirm_if_applicable()

    @allowed_states([GS.NEW, GS.CONFIRMED])
    def cancel_gap(self):
        was_long = self.is_long_absence_and_needs_approval()
        self._set_state(GS.CANCELED)

        self._update()

        approver = self._get_approver()
        self._append_chief_for_notify(approver)

        self._cancel_gap_email(approver=approver)
        if was_long:
            self.issue_comment('cancel_long_absence', forbidden_statuses=['cancelled'])

    def _auto_confirm_if_applicable(self):
        if self.gap['state'] == GS.CONFIRMED:
            return
        has_no_chiefs = not self._chiefs_followers()
        if has_no_chiefs:
            self.confirm_gap()

    @allowed_states([GS.NEW])
    def confirm_gap(self):
        if not self.is_long_absence_and_needs_approval():
            return HttpResponseBadRequest('Confirmation is not allowed for this gap')

        self._set_state(GS.CONFIRMED)
        self.gap['confirmed_by_id'] = self.modifier['id']

        self._update()
        self.issue_comment('confirm_long_absence', forbidden_statuses=['closed', 'cancelled'])

    @classmethod
    def long_absences_query_builder(cls):
        return (
            GapQueryBuilder()
            .workflow(cls.workflow)
            .op_or([
                {'full_day': True},
                {'duration': {'$gt': cls.LONG_ABSENCE_HOURS}},
            ])
        )

    def is_long_absence(self):
        if waffle.switch_is_active('disable_long_absence'):
            return False

        result = (
            self.gap['full_day']
            or self.gap['duration'] > AbsenceWorkflow.LONG_ABSENCE_HOURS
        )

        return result

    def is_long_absence_and_needs_approval(self):
        if waffle.switch_is_active('disable_long_absence'):
            return False

        result = self.gap['need_approval'] and self.is_long_absence()
        return result

    def person_can_confirm_gap(self, login):
        followers = self._chiefs_followers()

        if not followers:
            return False

        return followers[-1] == login

    def confirm_by_chief_haction(self, haction_hash):
        assert haction_hash in self.gap['hactions']
        self.gap['hactions'].pop(haction_hash)
        try:
            self.confirm_gap()
        except WrongWorkflowStateError:
            self._update()

    def _set_duration(self):
        if not self.gap['full_day']:
            duration = (self.gap['date_to'] - self.gap['date_from']).total_seconds()
            self.gap['duration'] = duration // 3600

    def _update_confirm_action_hash(self):
        if not self.is_long_absence_and_needs_approval():
            self.gap['hactions'] = {}
            return

        approver = self._get_approver()
        approver_id = approver['id'] if approver else None

        self.gap['hactions'] = confirm_by_chief_hash_action(
            self.person['login'],
            self.person['id'],
            self.modifier['id'],
            approver_id,
        )

    def _set_need_approval(self):
        if not self.is_long_absence():
            self.gap['need_approval'] = False
            return

        gap_year = self.gap['date_from'].year
        first_day_of_the_year = datetime(year=gap_year, month=1, day=1)
        first_day_of_the_next_year = datetime(year=gap_year + 1, month=1, day=1)

        query = (
            self.long_absences_query_builder()
            .person_login(self.person['login'])
            .date_from(first_day_of_the_year)
            .date_to(first_day_of_the_next_year)
            .query()
        )

        long_absences_count = GapCtl().find(query).count()
        self.gap['need_approval'] = long_absences_count > self.LONG_ABSENCES_WO_APPROVAL
