from datetime import date
from itertools import chain
import logging
from typing import Dict, List, Tuple

from staff.departments.models import Department
from staff.headcounts.headcounts_summary import DepartmentCounters, HeadcountsSummaryCalculator
from staff.lib.db import atomic
from staff.lib.models.mptt import get_heirarchy_filter_query

from staff.headcounts_history.models import DepartmentHeadcountSnapshot

logger = logging.getLogger(__name__)


COUNTER_FIELDS = (
    'total_hc',
    'current_count',
    'offers',
    'current_balance',
    'vacancy_open',
    'vacancy_plan',
    'vacancy_plan_new',
    'vacancy_plan_replacement',
    'reserve_new',
    'crossheadcount',
    'replacement_to_working',
    'offers_for_working_replacement',
    'vacancy_open_for_working_replacement',
    'vacancy_plan_for_working_replacement',
    'overdraft_percents_with_child',
)


def _make_department_headcount_snapshot(
    today: date,
    department: Department,
    department_counters: DepartmentCounters,
) -> DepartmentHeadcountSnapshot:
    kwargs = {field: getattr(department_counters, field) for field in COUNTER_FIELDS}
    return DepartmentHeadcountSnapshot(
        date_from=today,
        department_id=department.id,
        name=department.name,
        name_en=department.name_en,
        **kwargs,
    )


def _branch_descendants_ids(branch_department_id: int) -> List[int]:
    branch_filter = get_heirarchy_filter_query(
        mptt_objects=[Department.objects.get(id=branch_department_id)],
        by_children=True,
        include_self=True,
    )

    yandex_descendants = (
        Department.objects
        .filter(branch_filter)
        .filter(intranet_status=1)
        .values_list('id', flat=True)
    )
    return list(yandex_descendants)


def _get_existing_department_counters(department_ids: List[int]) -> List[Tuple[Department, DepartmentCounters]]:
    cal = HeadcountsSummaryCalculator()
    result = cal.get_summary_for_departments(departments_ids=department_ids, summary_with_children=True)
    departments = Department.objects.filter(id__in=department_ids)
    return [
        (department, result[department.id])
        for department in departments
        if department.id in result
    ]


def _get_previous_snapshots(department_ids: List[int], today: date) -> Dict[int, DepartmentHeadcountSnapshot]:
    qs = DepartmentHeadcountSnapshot.objects.filter(date_to__gte=today, department__in=department_ids)
    result = {snapshot.department_id: snapshot for snapshot in qs}
    return result


def _delete_today_snapshots(today: date, department_ids: List[int]):
    (
        DepartmentHeadcountSnapshot.objects
        .filter(date_from__gte=today)
        .filter(department_id__in=department_ids)
        .delete()
    )


def _reopen_previous(today: date, department_ids: List[int]):
    (
        DepartmentHeadcountSnapshot.objects
        .filter(date_to=today)
        .filter(department_id__in=department_ids)
        .update(date_to=date.max)
    )


def _has_changes(first: DepartmentHeadcountSnapshot, second: DepartmentHeadcountSnapshot) -> bool:
    for field in chain(COUNTER_FIELDS, ('name', 'name_en')):
        if getattr(first, field) != getattr(second, field):
            return True

    return False


def make_daily_snapshot(today: date, branch_department_id: int):
    logger.info('Calculating current counters for department %s', branch_department_id)
    involved_department_ids = _branch_descendants_ids(branch_department_id)
    department_counters = _get_existing_department_counters(involved_department_ids)
    logger.info('Found %s counters', len(department_counters))

    with atomic():
        _delete_today_snapshots(today, involved_department_ids)
        _reopen_previous(today, involved_department_ids)
        previous_snapshots = _get_previous_snapshots(involved_department_ids, today)

        to_create = []
        to_close = []
        for department, counters in department_counters:
            snapshot = _make_department_headcount_snapshot(today, department, counters)

            if department.id not in previous_snapshots:
                to_create.append(snapshot)
            else:
                if _has_changes(snapshot, previous_snapshots[department.id]):
                    to_create.append(snapshot)
                    to_close.append(previous_snapshots[department.id].id)

        DepartmentHeadcountSnapshot.objects.bulk_create(objs=to_create)
        DepartmentHeadcountSnapshot.objects.filter(id__in=to_close).update(date_to=today)
        logger.info('Snapshot for department %s is done')
