from itertools import chain

from datetime import date, timedelta, datetime
from typing import Optional, List, Dict, Any

from staff.lib.utils.ordered_choices import StaffChoices

from staff.trip_questionary.models import EVENT_TYPE


ISSUE_STATUS = StaffChoices(
    APPROVED=('approved', 'approved'),  # id=25
    CLOSED=('closed', 'closed'),  # id=3
    CHECK_IN=('checkIn', 'check_in'),  # id=28
    OPEN=('open', 'open'),  # id=1
    TRIP_READY=('tripReady', 'trip_ready'),  # id=282
    HR_READY=('hrReady', 'hr_ready'),  # id=283
    NEED_AGREEMENT=('needAgreement', 'need_agreement'),
    NEED_INFO=('needInfo', 'need_info'),
    IN_PROGRESS=('inProgress', 'in_progress'),
    REQUEST=('request', 'request'),
    VERIFIED=('verified', 'verified'),
    RECIEVED=('recieved', 'recieved'),
    ADVANCE_PAID=('advancePaid', 'advance_paid'),
    PAYMENT_OF_ADVANCE=('paymentOfAdvance', 'payment_of_advance'),
    PARTIALLY_REPORTED=('partiallyReported', 'partially_reported'),
    PREPARATION=('preparation', 'preparation'),
)

ISSUE_RESOLUTION = StaffChoices(
    FIXED=('fixed', 'fixed'),  # id=1
    WONTFIX=("won'tFix", 'wontfix'),  # id=2
)

GAP_STATES = StaffChoices(
    # старый статус, теперь используется CONFIRMED
    CREATED=('created', 'created'),

    NEW=('new', 'new'),
    CONFIRMED=('confirmed', 'confirmed'),
    CANCELED=('canceled', 'canceled'),
)


def default_chiefs_cutter(chiefs_list):
    return chiefs_list


def cut_last_chief(chiefs_list):
    return chiefs_list[:-1]


def leave_only_first_chief(chiefs_list):
    if not chiefs_list:
        return []
    return chiefs_list[:1]


SPECIAL_CONF_CHIEFS_CUTTERS = {
    'yandex_search_tech_sq': cut_last_chief,
    'yandex_infra_data': cut_last_chief,
    'yandex_main_searchadv_dep85477': cut_last_chief,
    'yandex_rkub_portal_management': cut_last_chief,
    'yandex_main_searchadv_9865': cut_last_chief,
    'yandex_rkub_taxi': leave_only_first_chief,
}


def get_resolution_id(employee, event_type):
    issue_key = event_type + '_issue'
    return (
        employee and
        employee[issue_key] and
        'resolution' in employee[issue_key] and
        employee[issue_key]['resolution'] and
        employee[issue_key]['resolution']['key']
    )


def event_types_of(trip):
    et = trip.data['event_type']

    if et in (EVENT_TYPE.TRIP, EVENT_TYPE.TRIP_CONF):
        yield 'trip'

    if et in (EVENT_TYPE.CONF, EVENT_TYPE.TRIP_CONF):
        yield 'conf'


def datetime_to_date(dt):
    if isinstance(dt, datetime):
        return dt.date()
    return dt


class TripConditions(object):

    def __init__(self, trip, diff=None, **kwargs):
        self.trip = trip
        self.diff = diff
        for name, value in kwargs.items():
            setattr(self, name, value)

    @property
    def is_new(self):
        return self.trip.is_new

    @property
    def is_single(self):
        return len(self.trip.data['employee_list']) == 1

    @property
    def is_group(self):
        return not self.is_single

    @property
    def is_taxi_account_active(self):
        return self.employee_data.get('is_taxi_account_active', False)

    @property
    def is_employee_needs_taxi(self):
        return self.employee_data.get('need_taxi')

    @property
    def is_trip(self):
        return self.trip.data['event_type'] == EVENT_TYPE.TRIP

    @property
    def are_taxi_dates_correct(self):
        """Даты выдачи корп. такси есть и заполнены корректно."""
        if not self.is_trip_employee_issue_created:
            return False

        trip_date_from = datetime_to_date(self.trip.data['trip_date_from'])
        trip_date_to = datetime_to_date(self.trip.data['trip_date_to'])

        taxi_start_date = datetime_to_date(self.employee_data['trip_issue'].get('taxiStartDate'))
        taxi_end_date = datetime_to_date(self.employee_data['trip_issue'].get('taxiEndDate'))

        if taxi_start_date is None or taxi_end_date is None:
            return False

        taxi_min = trip_date_from - timedelta(days=20)
        taxi_max = trip_date_to + timedelta(days=20)

        return taxi_min <= taxi_start_date and taxi_end_date <= taxi_max

    @property
    def is_between_taxi_interval(self):
        """Сегодня попадаем в даты выдачи корп. такси"""
        if not self.are_taxi_dates_correct:
            return False

        taxi_start_date = datetime_to_date(
            self.employee_data['trip_issue']['taxiStartDate']
        )
        taxi_end_date = datetime_to_date(
            self.employee_data['trip_issue']['taxiEndDate']
        )

        return taxi_start_date <= date.today() <= taxi_end_date

    @property
    def is_conf(self):
        return self.trip.data['event_type'] == EVENT_TYPE.CONF

    @property
    def is_trip_conf(self):
        return self.trip.data['event_type'] == EVENT_TYPE.TRIP_CONF

    @property
    def is_foreign(self):
        return self.trip.is_foreign()

    # ISSUES

    @staticmethod
    def is_issue_created(obj: Dict[str, Any], event_type: str):
        assert event_type in (EVENT_TYPE.CONF, EVENT_TYPE.TRIP)
        issue_field = event_type + '_issue'
        return issue_field in obj

    def is_toplevel_issue_created(self, event_type):
        return self.is_issue_created(self.trip.data, event_type)

    @property
    def is_trip_toplevel_issue_created(self):
        return self.is_toplevel_issue_created('trip')

    @property
    def is_conf_toplevel_issue_created(self):
        return self.is_toplevel_issue_created('conf')

    @property
    def is_all_toplevel_issues_created(self):
        return all(
            self.is_toplevel_issue_created(et)
            for et in event_types_of(self.trip)
        )

    def is_personal_issues_created(self, event_type):
        return all(
            self.is_issue_created(employee_data, event_type)
            for employee_data in self.trip.data['employee_list']
        )

    @property
    def is_trip_personal_issues_created(self):
        return self.is_personal_issues_created('trip')

    @property
    def is_conf_personal_issues_created(self):
        return self.is_personal_issues_created('conf')

    @property
    def is_all_personal_issues_created(self):
        return all(
            self.is_personal_issues_created(et)
            for et in event_types_of(self.trip)
        )

    @property
    def is_trip_employee_issue_created(self):
        return self.is_issue_created(self.employee_data, EVENT_TYPE.TRIP)

    @property
    def is_conf_employee_issue_created(self):
        return self.is_issue_created(self.employee_data, EVENT_TYPE.CONF)

    ####

    @property
    def is_employee_needs_approve(self):
        return bool(self.trip.get_approver(self.employee_data['employee']))

    @property
    def is_employee_approval_started(self):
        return self.employee_data.get('is_approval_started')

    @property
    def employee_need_mobile_additional_packages(self):
        return bool(
            self.employee_data.get('need_mobile_additional_packages')
            and self.employee_data['mobile_packages']
        )

    def _trip_or_conf_only(self):
        return EVENT_TYPE.TRIP if self.is_trip else EVENT_TYPE.CONF

    def _get_event_type(self, forced_event_type: Optional[str] = None) -> Optional[str]:
        event_type = forced_event_type or self._trip_or_conf_only()
        if not self.is_issue_created(self.employee_data, event_type):
            return
        return event_type

    def check_status_equals(self, *statuses_to_check: List[str], forced_event_type: Optional[str] = None):
        event_type = self._get_event_type(forced_event_type=forced_event_type)
        return event_type and self.get_status(event_type) in statuses_to_check

    def get_status(self, event_type=None):
        event_type = event_type or self._get_event_type()
        return self.employee_data[event_type + '_issue']['status']['key']

    def check_resolution_equals(self, *args, **kwargs):
        event_type = self._get_event_type(**kwargs)
        return event_type and get_resolution_id(self.employee_data, event_type) in args

    @property
    def is_employee_approved(self):
        return self.check_status_equals(ISSUE_STATUS.APPROVED)

    def is_employee_closed(self, forced_event_type=None):
        return (
            self.check_status_equals(ISSUE_STATUS.CLOSED, forced_event_type=forced_event_type) and
            self.check_resolution_equals(ISSUE_RESOLUTION.FIXED, forced_event_type=forced_event_type)
        )

    def is_employee_trip_ready(self, forced_event_type=None):
        return self.check_status_equals(ISSUE_STATUS.TRIP_READY, forced_event_type=forced_event_type)

    def is_employee_hr_ready(self, forced_event_type=None):
        return self.check_status_equals(ISSUE_STATUS.HR_READY, forced_event_type=forced_event_type)

    @property
    def is_employee_open(self):
        return self.check_status_equals(ISSUE_STATUS.OPEN)

    @property
    def is_employee_need_agreement(self):
        return self.check_status_equals(ISSUE_STATUS.NEED_AGREEMENT)

    @property
    def is_employee_check_in(self):
        return self.check_status_equals(ISSUE_STATUS.CHECK_IN)

    @property
    def is_employee_canceled(self):
        return (
            self.check_status_equals(ISSUE_STATUS.CLOSED) and
            self.check_resolution_equals(ISSUE_RESOLUTION.WONTFIX)
        )

    @property
    def is_ticket_approved(self):
        return self.check_status_equals(
            ISSUE_STATUS.APPROVED,
            ISSUE_STATUS.CHECK_IN,
            ISSUE_STATUS.TRIP_READY,
            ISSUE_STATUS.NEED_AGREEMENT,
            ISSUE_STATUS.NEED_INFO,
            ISSUE_STATUS.IN_PROGRESS,
            ISSUE_STATUS.REQUEST,
            ISSUE_STATUS.VERIFIED,
            ISSUE_STATUS.RECIEVED,
            ISSUE_STATUS.ADVANCE_PAID,
            ISSUE_STATUS.PAYMENT_OF_ADVANCE,
            ISSUE_STATUS.PARTIALLY_REPORTED,
            ISSUE_STATUS.PREPARATION,
        ) or self.is_employee_closed()

    @property
    def is_employee_interested_user_list(self):
        return bool(self.employee_data['interested_user_list'])

    @property
    def is_gap_created(self):
        return 'gap' in self.employee_data

    @property
    def is_gap_new(self):
        return (
            self.is_gap_created
            and
            self.employee_data['gap']['state'] == GAP_STATES.NEW
        )

    @property
    def is_gap_canceled(self):
        return (
            self.is_gap_created
            and
            self.employee_data['gap']['state'] == GAP_STATES.CANCELED
        )

    @property
    def is_gap_approved(self):
        return (
            self.is_gap_created
            and
            self.employee_data['gap']['state'] in (
                GAP_STATES.CREATED,
                GAP_STATES.CONFIRMED
            )
        )

    @property
    def is_private(self):
        return self.employee_data['is_private']

    @property
    def is_creation_notification_sent(self):
        return self.trip.data.get('is_creation_notification_sent', False)

    @property
    def need_send_access_notifications(self):
        for accessor, people in self.trip.get_notified_accessors().items():
            if not people:
                continue

            already_notificated = (
                self.trip.data
                .get('notificated_chiefs', {})
                .get(str(accessor.id), [])
            )

            if bool(set(people) - set(already_notificated)):
                return True

        return False

    @property
    def is_all_issues_links_created(self):
        return all(
            d.get('is_issue_link_created', False)
            for d in chain([self.trip.data], self.trip.data['employee_list'])
        )

    # diff
    @property
    def is_new_employee(self):
        # EmployeeDatesEditOperation
        # HRMobile
        # OnTripIssueCloseEmail
        # NoticeEmail
        if self.diff['employee_list'].is_simple:
            return True
        return self.employee_diff.is_simple

    @property
    def is_new_city(self):
        # CityDatesEditOperation
        if self.diff['city_list'].is_simple:
            return True
        return self.city_diff.is_simple

    @property
    def is_city_dates_changed(self):
        # CityDatesEditOperation
        return self.diff and (
            self.city_diff['departure_date'].is_changed
            or
            self.city_diff['city_arrive_date_type'].is_changed
        )

    @property
    def is_trip_dates_changed(self):
        # NoticeEmail
        fields = ('trip_date_from', 'trip_date_to')
        return any(self.diff[field].is_changed for field in fields)

    @property
    def is_trip_info_changed(self):
        # NoticeEmail
        return self.employee_diff['trip_info'].is_changed

    @property
    def is_trip_purpose_changed(self):
        return self.diff['purpose'].is_changed

    @property
    def is_event_dates_changed(self):
        fields = ('event_date_from', 'event_date_to')
        return any(self.diff[field].is_changed for field in fields)

    @property
    def is_employee_interested_user_list_changed(self):
        # NoticeEmail
        return self.employee_diff['interested_user_list'].is_changed

    def is_employee_just_trip_ready(self, forced_event_type=None):
        # OnTripIssueCloseEmail
        event_type = forced_event_type or self._trip_or_conf_only()
        event_issue = event_type + '_issue'
        if event_issue not in self.employee_diff:
            return False

        status_id = self.employee_diff[event_issue]['status']['key']

        return (
            status_id.is_changed
            and status_id.right == ISSUE_STATUS.TRIP_READY
        )

    @property
    def is_employee_just_approved(self):
        # HRMobile
        # NoticeEmail
        event_issue = self._trip_or_conf_only() + '_issue'
        if event_issue not in self.employee_diff:
            return False

        status_id = self.employee_diff[event_issue]['status']['key']

        return (
            status_id.is_changed
            and
            status_id.left != ISSUE_STATUS.APPROVED
            and
            (
                self.is_employee_approved
                or
                self.is_employee_closed()
            )
        )

    @property
    def is_return_city_added(self):
        # ReturnCityAddedOperation
        # NoticeEmail
        # Если добавился город, считаем, что это обратный маршрут,
        # так как нельзя просто взять и добавить город при редактировании,
        # кроме как обратный маршрут
        return self.diff['city_list'].added_count == 1

    @property
    def is_employee_departure_date_changed(self):
        # EmployeeDepartureDateEditOperation
        return self.employee_diff['departure_date'].is_changed

    @property
    def is_employee_return_date_changed(self):
        # EmployeeReturnDateEditOperation
        return self.employee_diff['return_date'].is_changed
