# coding: utf-8
from __future__ import unicode_literals

import logging
import math
import operator
from datetime import date, datetime, timedelta
from decimal import Decimal

import arrow
from cia_stuff.oebs import utils as oebs_utils

from cab import const
from cab.sources import review, staff_api, startrek_goals
from cab.utils import datetimes, dehydration, dicts, exceptions, l10n, std

from . import const as persons_const


log = logging.getLogger(__name__)


def process_last_activity(data):
    last_activity = l10n.localized(data)
    updated_at = arrow.get(last_activity['updated_at'])
    is_vpn = last_activity['is_vpn']

    if is_vpn:
        office = 'VPN'
    else:
        office = last_activity['office']['name']
    return {
        'office': office,
        'seconds_ago': datetimes.seconds_ago(updated_at),
    }


def process_staff_api_person_field(data, field):
    data = l10n.localized(data)
    api_field = persons_const.STAFF_API_FIELDS_MAPPING[field]
    return dicts.get_nested(data, api_field)


def process_department(data):
    field_data = process_staff_api_person_field(data, 'department')
    return {
        'url': field_data['department']['url'],
        'name': field_data['department']['name']['full'],
    }


def process_superior(data):
    chief = data['chief']
    if not chief:
        return
    chief = l10n.localized(chief)
    return {
        'login': chief['login'],
        'name': chief['name'],
    }


def process_staff_personal_field(data, field):
    result_field_name = {
        'vacation_days_accumulated': 'vacation',
    }.get(field, field)

    float_fields = (
        'vacation'
    )

    value = data.get(result_field_name)
    if value is None:
        return persons_const.FIELD_ACCESS_DENIED

    if result_field_name in float_fields:
        if not isinstance(value, float):
            log.error('Expected float, got %s (%s)', type(value), value)
            return persons_const.FIELD_PROCESS_ERROR
        return int(math.floor(value))
    else:
        return value


def process_vacation_days_used(data):
    today = datetimes.today()
    year_start = date(today.year, 1, 1)

    days_used_from_year_start = 0
    for gap in data:
        days_used_from_year_start += gap.intersection_with(
            range_start=year_start,
            range_end=today,
        )
    return days_used_from_year_start


def process_vacation_days_planned(data):
    today = datetimes.today()

    days_planned_from_today = 0
    for gap in data:
        days_planned_from_today += gap.intersection_with(
            range_start=today,
            range_end=None,
        )
    return days_planned_from_today


def process_last_vacation(data):
    today = datetimes.today()

    last_gap = None
    for gap in data:
        if gap.first_day > today:
            continue

        if last_gap is None or last_gap.first_day < gap.first_day:
            last_gap = gap
    return last_gap and {
        'from': last_gap.first_day,
        'to': last_gap.last_day,
        'duration': last_gap.duration,
    }


def process_goals_count(data):
    return len(data)


def process_key_goals_involvement(data):
    return len([
        g for g in data
        if g['importance'] in (startrek_goals.GI_DEPARTMENT, startrek_goals.GI_COMPANY)
    ]) > 0


def _get_latest_salary(data):
    history = data and data.get('salary_history')
    if not history:
        return None
    return oebs_utils.SalaryHistory(history).latest()


def process_salary_last_year(data):
    to_date = datetimes.today()
    history_months_count = 12
    result = _process_salary_for_range(data, to_date, months=-history_months_count)
    return {
        'value': result['value'],
        'currency': result['currency'],
    }


def _process_salary_for_range(data, to_date=None, **shift_kwargs):
    salary_history = data.get('salary_history')
    from_date = datetimes.shifted_date(to_date, **shift_kwargs)
    salary_history = oebs_utils.SalaryHistory(salary_history)
    last_year_salary_history = salary_history.get_range(
        from_date=from_date,
        to_date=to_date,
    )
    closest_history = salary_history.get_closest_before(from_date)
    if not last_year_salary_history:
        if not closest_history:
            return {
                'from_date': None,
                'value': 0,
                'currency': const.RUB_CURRENCY,
            }
        last_salary = salary_history.get_value_from_item(closest_history)
        months_count = shift_kwargs.get('months') or shift_kwargs.get('years') * 12
        months_count = abs(months_count)
        value = last_salary * months_count
    else:
        if closest_history is None:
            closest_history = last_year_salary_history.earliest()
            last_salary = last_year_salary_history.get_value_from_item(closest_history)
            last_date = last_year_salary_history.get_date_from_item(closest_history)
            from_date = last_date
        else:
            last_salary = salary_history.get_value_from_item(closest_history)
            last_date = from_date

        value = Decimal()
        days_in_month = Decimal.from_float(persons_const.AVG_DAYS_IN_MONTH)
        for salary_change in last_year_salary_history:
            change_date = last_year_salary_history.get_date_from_item(salary_change)
            change_value = last_year_salary_history.get_value_from_item(salary_change)
            if not change_value:
                continue
            date_delta = change_date - last_date
            date_delta_in_month = date_delta.days / days_in_month
            value += last_salary * date_delta_in_month
            last_salary = change_value
            last_date = change_date

        date_delta = to_date - last_date
        date_delta_in_month = date_delta.days / days_in_month
        value += last_salary * date_delta_in_month
    currency = salary_history.latest()['currency']
    return {
        'from_date': from_date,
        'value': int(value),
        'currency': currency
    }


def process_bonus_last_year(data):
    to_date = datetimes.today()
    history_months_count = 12
    from_date = datetimes.shifted_date(to_date, months=-history_months_count)
    return _process_bonus_for_range(data, to_date, from_date)


def _process_bonus_for_range(data, to_date=None, from_date=None):
    bonus_history = data.get('bonus_history')

    if not bonus_history:
        return {
            'value': 0,
            'currency': const.RUB_CURRENCY,
        }
    bonus_history = oebs_utils.BonusHistory(bonus_history)
    range_bonus = bonus_history.get_range_values_sum(
        from_date=from_date,
        to_date=to_date,
    )
    currency = bonus_history.latest()['currency']
    return {
        'value': range_bonus,
        'currency': currency
    }


def _get_options_monetary_for_date_range(data, from_date, to_date):
    last_salary_currency = _get_last_salary_currency(data)
    data = data.get('options_history', [])
    merged_options = review.merge_options_with_money(
        options=data,
        currency=last_salary_currency,
    )
    result = []
    for code, vesting in merged_options.items():
        vesting_events = review.VestingValueRange(vesting)
        vested_value = vesting_events.get_range_values_sum(
            from_date=from_date,
            to_date=to_date,
        )
        result.append(vested_value)
    return {
        'value': int(sum(result)),
        'currency': last_salary_currency,
    }


def process_options_monetary_last_year(data):
    today = datetimes.today()
    history_months_count = 12
    from_date = datetimes.today_shifted(months=-history_months_count)
    return _get_options_monetary_for_date_range(data, from_date, today)


def process_options_monetary_forecast(data):
    forecast_months_count = 12
    today = datetimes.today()
    to_date = datetimes.today_shifted(months=forecast_months_count)
    return _get_options_monetary_for_date_range(data, today, to_date)


def process_income_last_year(data):
    salary_last_year = process_salary_last_year(data)
    bonus_last_year = process_bonus_last_year(data)
    options_last_year = process_options_monetary_last_year(data)
    return {
        'value': salary_last_year['value'] + bonus_last_year['value'] + options_last_year['value'],
        'currency': const.RUB_CURRENCY,
    }


def _calculate_bonus_part(bonus_history, salary_history, from_date, to_date):

    if not salary_history:
        return 0

    bonus_range = bonus_history.get_range(from_date, to_date)

    bonus_part_of_salary = Decimal(0)
    for bonus_event in bonus_range:
        bonus_date = bonus_history.get_date_from_item(bonus_event)
        salary_on_that_moment = salary_history.get_closest_before(
            date=bonus_date
        )
        salary_val = salary_on_that_moment['salarySum']
        bonus_val = bonus_event['summ']
        if not salary_val or not bonus_val:
            raise exceptions.CabException(
                'Unexpected values for salary: %s or bonus: %s',
                salary_val,
                bonus_val
            )
        if salary_on_that_moment['currency'] != bonus_event['currency']:
            raise exceptions.CabException(
                'Currencies of salary: %s and bonus: %s not matched',
                salary_on_that_moment['currency'],
                bonus_event['currency'],
            )
        bonus_part_of_salary += Decimal(bonus_val) / Decimal(salary_val)

    working_part_in_range = Decimal(1)
    first_work_day = salary_history.get_date_from_item(salary_history.earliest())

    range_days = (to_date - from_date).days
    if first_work_day > from_date:
        worked_for_days = (first_work_day - from_date).days
        worked_for_days = Decimal(worked_for_days)
        working_part_in_range = 1 - worked_for_days / range_days
    bonus_part_of_salary /= working_part_in_range

    return bonus_part_of_salary


def _process_options_vesting_forecast(data, **range_params):
    last_salary_currency = _get_last_salary_currency(data)
    merged_options = review.merge_options_with_money(
        options=data['options_history'],
        currency=last_salary_currency,
    )
    today = datetimes.today()
    if range_params:
        forecast_end = datetimes.today_shifted(**range_params)
    else:
        forecast_end = None

    result = []
    for code, vesting in merged_options.items():
        vesting_events = oebs_utils.VestingRange(vesting)
        vested_amount = vesting_events.get_range_values_sum(
            from_date=today,
            to_date=forecast_end,
        )
        vesting_events_value = review.VestingValueRange(vesting)
        vested_value = vesting_events_value.get_range_values_sum(
            from_date=today,
            to_date=forecast_end,
        )
        if vested_amount:
            result.append({
                'grant_type': code,
                'amount': vested_amount,
                'value': int(vested_value),
                'currency': last_salary_currency,
            })
    return result


def _get_last_salary_currency(data):
    if not data.get('salary_history'):
        return const.RUB_CURRENCY
    history = oebs_utils.SalaryHistory(data['salary_history'])
    latest = history.latest()
    return latest and latest['currency'] or const.RUB_CURRENCY


def process_options_vesting_forecast(data):
    return _process_options_vesting_forecast(
        data=data,
        months=12,
    )


def process_options_vesting_forecast_total(data):
    return _process_options_vesting_forecast(data=data)


def process_options_vesting_forecast_count(data):
    return [
        {
            'grant_type': option_data['grant_type'],
            'amount': option_data['amount'],
        }
        for option_data in _process_options_vesting_forecast(
            data=data,
            months=12,
        )
    ]


def process_options(data):
    last_salary_currency = _get_last_salary_currency(data)
    merged_options = review.merge_options_with_money(
        options=data['options_history'],
        currency=last_salary_currency,
    )
    result = []
    for code, vesting in merged_options.items():
        vesting_amount_range = oebs_utils.VestingRange(vesting)
        vested_amount = vesting_amount_range.get_range_values_sum()
        vesting_value_range = review.VestingValueRange(vesting)
        vested_value = vesting_value_range.get_range_values_sum()
        result.append({
            'grant_type': code,
            'amount': vested_amount,
            'value': int(vested_value),
            'currency': last_salary_currency,
        })
    return result


def process_options_vested(data):
    last_salary_currency = _get_last_salary_currency(data)
    merged_options = review.merge_options_with_money(
        options=data['options_history'],
        currency=last_salary_currency,
    )
    today = datetimes.today()

    result = []
    for code, vesting in merged_options.items():
        vesting_amount_range = oebs_utils.VestingRange(vesting)
        vested_amount = vesting_amount_range.get_range_values_sum(to_date=today)
        vesting_value_range = review.VestingValueRange(vesting)
        vested_value = vesting_value_range.get_range_values_sum(to_date=today)
        result.append({
            'grant_type': code,
            'amount': vested_amount,
            'value': int(vested_value),
            'currency': last_salary_currency,
        })
    return result


def process_fte(data):
    for assignment in data:
        if assignment['main']:
            return assignment['rate']
    return 0


def process_grade(data):
    data = data.get('grade_history')
    if not data:
        return persons_const.FIELD_NO_DATA
    else:
        current_grade = oebs_utils.GradeHistory(data).latest()
        return dehydration.grade(current_grade)


def process_last_salary_raise(data):
    data = data.get('salary_history')
    if data is None:
        return None
    sorted_history = sorted(data, key=operator.itemgetter('dateFrom'))
    last_raise = None
    previous_salary = 0
    for event in sorted_history:
        if event['salarySum'] > previous_salary:
            last_raise = event['dateFrom']
        previous_salary = event['salarySum']
    return last_raise


def process_last_bonus_payment(data):
    data = data.get('bonus_history')
    if data is None:
        return None
    sorted_history = sorted(
        data,
        key=operator.itemgetter('effectiveDate'),
        reverse=True,
    )
    last_payment = std.get_first_or_none(sorted_history)
    return last_payment and last_payment['effectiveDate']


def process_loans(data):
    data = data.get('current_loans')
    if data is None:
        return None
    return [
        {
            'balance': int(loan_data['balance']),
            'total': int(loan_data.get('loanSum')),
            'currency': loan_data['currency'],
            'loan_type': int(loan_data['loanType']),
        }
        for loan_data in data
    ]


def process_social_package(data):
    data = data.get('social_package')
    if data is None:
        return None

    return {
        key: True if value == 'Y' else False
        for key, value in data.items()
        if key in (
            'food',
            'mobile',
            'vhi',
            'internet',
        )
    }


def process_marks(data):
    marks = [
        item['mark']
        for item in process_reviews(data)
    ]
    # фронтенд почему-то выводит их в обратном порядке
    # (вероятно у нас раньше криво отдавалось)
    return list(reversed(marks))


def process_reviews(data):
    sorted_person_reviews = sorted(
        data,
        key=lambda pr: pr['review']['start_date'],
    )
    person_reviews = []
    for person_review in sorted_person_reviews:
        if person_review['review']['type'] != 'normal':
            continue
        if person_review['mark'] == review.DISABLED:
            mark = '-'
        else:
            mark = person_review['mark']
        person_reviews.append({
            'mark': mark,
            'start_date': person_review['review']['start_date'],
            'goldstar': review.get_goldstar_value(person_review['goldstar']),
        })
    return person_reviews[-3:]


def process_bonus_options(data):
    return any(review_result['options_rsu'] for review_result in data)


def process_gap(data):
    main_gap = std.get_first_or_none(data)
    return main_gap['workflow']


def process_subordinate_departments(data):
    result = []
    for department in data:
        localized_dep = l10n.localized(department)['department']
        result.append({
            'url': localized_dep['url'],
            'name': localized_dep['name']['full'],
        })
    return result


def process_subordinate_persons_field(data, field):
    login = data['login']
    result_set = data['subordinates']

    if field == 'total_subordinates_count':
        return result_set.total

    # очень сложно посчитать
    if result_set.pages > 1:
        if field == 'direct_subordinates':
            return []
        if field == 'direct_subordinates_count':
            return None
        if field == 'total_subordinates':
            return []

    direct_subordinates = []
    for person in result_set:
        chief = person['chief']
        if chief and chief['login'] == login:
            direct_subordinates.append(person['login'])
    total_subordinates = [person['login'] for person in result_set]

    if field == 'direct_subordinates':
        return direct_subordinates
    if field == 'direct_subordinates_count':
        return len(direct_subordinates)
    if field == 'total_subordinates':
        return total_subordinates


def _get_last_period(periods, forecast=False):
    sign = (forecast * 2 - 1)
    return sorted(
        periods,
        key=lambda x: (sign * x['forecast'], datetime.strptime(x['to'], '%m/%d/%Y')),
    )[-1]


def _process_bi_data(data, field, forecast):
    periods = data['income']['from_current_date']
    period = _get_last_period(periods, forecast=forecast)
    return {
        'currency': data.get('currency'),
        'value': period.get(field),
        'date': datetime.strptime(period.get('from'), '%m/%d/%Y').date(),
        'dateTo': datetime.strptime(period.get('to'), '%m/%d/%Y').date(),
    }


def process_salary(data):
    return {
        'currency': data.get('currency'),
        'value': data.get('salary'),
        # Мы знаем наверняка, что зарплата из Ревьюшницы прилетает
        # по состоянию на последний день предыдущего месяца
        'date': date.today().replace(day=1) - timedelta(days=1)
    }


def process_salary_forecast(data):
    return _process_bi_data(data, field='salary', forecast=True)


def process_options_vesting_forecast_cost(data):
    return _process_bi_data(data, field='vesting', forecast=True)


def process_incomes(data):
    return _process_bi_data(data, field='income', forecast=False)


def process_income_forecast(data):
    return _process_bi_data(data, field='income', forecast=True)


def process_year_bonus_payment(data):
    return _process_bi_data(data, field='bonus', forecast=False)


def process_bonus_forecast(data):
    return _process_bi_data(data, field='bonus', forecast=True)


def process_signup(data):
    return _process_bi_data(data, field='signup', forecast=True)


def process_piecerate(data):
    return _process_bi_data(data, field='piecerate', forecast=False)
