from itertools import groupby
from typing import Dict, Iterable, Tuple

from django.db.models import QuerySet
from django.http import HttpResponseNotFound, HttpResponseForbidden, HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import get_language
from django.views.decorators.http import require_GET, require_http_methods

from staff.budget_position.models import BudgetPositionAssignment
from staff.departments.models import Department, RelevanceDate, InstanceClass, HeadcountPosition
from staff.departments.tree_lib import TreeBuilder, Pager
from staff.dismissal.models import DISMISSAL_STATUS
from staff.femida.models import FemidaVacancy
from staff.lib.decorators import available_for_external, auth_by_tvm_only
from staff.lib.models.mptt import filter_by_heirarchy
from staff.lib.utils.qs_values import localize
from staff.lib.xlsx import make_xlsx_file_response
from staff.oebs.constants import PERSON_POSITION_STATUS
from staff.oebs.models import Reward

from staff.headcounts.budget_position_assignment_entity_info import BudgetPositionAssignmentEntityInfo
from staff.headcounts.budget_position_assignment_filter_context import BudgetPositionAssignmentFilterContext
from staff.headcounts.headcounts_export import (
    RU_HELP_PAGE_MODEL,
    EN_HELP_PAGE_MODEL,
    HelpPagePresenter,
    DepartmentsSummarySheetPresenter,
    SummaryModel,
    FlexiblePositionsSheetPresenter,
)
from staff.headcounts.headcounts_summary import HeadcountsSummaryCalculator, DepartmentCounters
from staff.headcounts.permissions import Permissions
from staff.headcounts.views.utils import find


def _departments_for_url(url):
    department = Department.all_types.get(url=url)

    qs = filter_by_heirarchy(
        BudgetPositionAssignmentFilterContext().departments_qs().order_by('tree_id', 'lft'),
        mptt_objects=[department],
        by_children=True,
        include_self=True,
    )

    return [localize(d) for d in qs]


def _headcounts_counters(
    departments,
    with_children: bool,
    valuestream_mode: bool,
) -> Dict[int, DepartmentCounters]:
    all_ids = [d['id'] for d in departments]

    summary_calculator = HeadcountsSummaryCalculator(valuestream_mode=valuestream_mode)
    result = summary_calculator.get_summary_for_departments(all_ids, with_children)
    return result


def _fill_departments_chains(departments):
    tree_builder = TreeBuilder(BudgetPositionAssignmentEntityInfo(BudgetPositionAssignmentFilterContext()))
    departments_chains = {dep['url']: dep['chain'] for dep in tree_builder.get_as_short_list(departments)}
    for department in departments:
        department['chain'] = departments_chains[department['url']]


def _departments_chains(department_ids):
    info_provider = BudgetPositionAssignmentEntityInfo(BudgetPositionAssignmentFilterContext())
    departments = info_provider.departments_query().filter(id__in=department_ids)
    return TreeBuilder(info_provider).get_departments_chains(departments)


def _fill_positions_chains(positions):
    filled_positions = []
    departments_ids = set()
    for position in positions:
        department_id = find('department.id', position)
        value_stream_id = find('value_stream.id', position)
        departments_ids.add(department_id)
        departments_ids.add(value_stream_id)
        filled_positions.append(position)
    chains = _departments_chains(departments_ids)
    for position in filled_positions:
        department_id = find('department.id', position)
        value_stream_id = find('value_stream.id', position)
        if department_id and department_id in chains:
            position['department']['chain'] = chains[department_id]
        if value_stream_id and value_stream_id in chains:
            position['value_stream']['chain'] = chains[value_stream_id]
        yield position


class TallPager(Pager):
    MAX_ROWS_COUNT = 9223372036854775807


def get_positions_with_department_info(departments_with_positions, department_url=None):
    for department in departments_with_positions:
        if department_url and department['url'] != department_url:
            continue
        for position in department['info'].get('positions', []):
            position.update({
                'department__{}'.format(name): value
                for name, value in department.items() if name != 'info'
            })
            yield position


@require_GET
@available_for_external('departments.can_export_ceilings')
def flexible_ceilings_export(request, url):
    permissions = Permissions(request.user.get_profile())

    try:
        if not permissions.has_access_to_department_url(url):
            return HttpResponseForbidden()

        filter_context = BudgetPositionAssignmentFilterContext(observer_permissions=permissions, exclude_reserve=False)
        filler = BudgetPositionAssignmentEntityInfo(filter_context)

        pager = TallPager(filler, url)
        positions_departments, _ = pager.get_grouped_entities()
        departments = _departments_for_url(url)
        _fill_departments_chains(departments)
    except Department.DoesNotExist:
        return HttpResponseNotFound()

    positions_without_children = get_positions_with_department_info(positions_departments, url)
    positions_with_children = get_positions_with_department_info(positions_departments)

    positions_without_children = _fill_positions_chains(positions_without_children)
    positions_with_children = _fill_positions_chains(positions_with_children)

    positions_page_wo_children = FlexiblePositionsSheetPresenter(
        positions_without_children,
        'Positions w.o. child',
    )

    positions_page_w_children = FlexiblePositionsSheetPresenter(
        positions_with_children,
        'Positions with child',
    )

    value_stream_mode = departments[0]['instance_class'] == InstanceClass.VALUESTREAM.value

    summary_page_wo_children = DepartmentsSummarySheetPresenter(
        SummaryModel(
            departments,
            _headcounts_counters(
                departments,
                with_children=False,
                valuestream_mode=value_stream_mode,
            )
        ),
        'Department summary w.o. child'
    )

    summary_page_w_children = DepartmentsSummarySheetPresenter(
        SummaryModel(
            departments,
            _headcounts_counters(
                departments,
                with_children=True,
                valuestream_mode=value_stream_mode,
            )
        ),
        'Department summary with child'
    )

    help_page = HelpPagePresenter(EN_HELP_PAGE_MODEL if get_language() == 'en' else RU_HELP_PAGE_MODEL)

    pages = [
        summary_page_w_children,
        positions_page_w_children,
        summary_page_wo_children,
        positions_page_wo_children,
        help_page
    ]

    relevance_date = RelevanceDate.objects.get(model_name=BudgetPositionAssignment.__name__).relevance_date
    file_name = f'ceilings_updated_on_{relevance_date}'
    return make_xlsx_file_response(pages, file_name)


@require_GET
@require_http_methods(['GET'])
@auth_by_tvm_only(['market'])
def hc_export(request, department_id):
    market_root = Department.objects.get(url='yandex_monetize_market')
    target_department = get_object_or_404(Department, pk=department_id)
    if not market_root.is_ancestor_of(target_department, include_self=True):
        return HttpResponseNotFound()

    headcounts = filter_by_heirarchy(
        query_set=HeadcountPosition.objects.all(),
        mptt_objects=[target_department],
        by_children=True,
        include_self=True,
        filter_prefix='department__',
    )
    rewards = Reward.objects.filter(pk__in=headcounts.values_list('reward_id', flat=True))
    rewards_map = {reward.pk: reward for reward in rewards}
    femida_vacancies = (
        FemidaVacancy.objects
        .filter(headcount_position_id__in=headcounts.values_list('code', flat=True))
        .order_by('headcount_position_id')
    )
    femida_vacancies_map = {
        headcount_id: tuple(femida_vacancies_group)
        for headcount_id, femida_vacancies_group in groupby(femida_vacancies, lambda fv: fv.headcount_position_id)
    }
    headcounts = (
        headcounts
        .select_related('current_person', 'department', 'department__parent')
        .prefetch_related('current_person__dismissals')
    )

    def get_page(queryset: QuerySet, cursor: str, page_size: int = 50) -> Tuple[QuerySet, str]:
        queryset = queryset.order_by('pk')
        if cursor is not None:
            queryset = queryset.filter(pk__gt=cursor)
        queryset = queryset[:page_size]
        last_cursor = queryset[len(queryset) - 1].pk if len(queryset) > 0 else None
        return queryset, last_cursor

    def serialize_hc(headcount: HeadcountPosition, reward: Reward, femida_vacancies: Iterable[FemidaVacancy]) -> dict:
        current_person = headcount.current_person
        parent_department = headcount.department.parent

        if current_person:
            dismissal = (
                current_person.dismissals
                .filter(status=DISMISSAL_STATUS.DONE)
                .order_by('-quit_date')
                .first()
            )
        else:
            dismissal = None

        return {
            'headcount_id': headcount.code,
            'department_id': headcount.department_id,
            'parent_department_id': parent_department and parent_department.pk,
            'group_id': headcount.department.group and headcount.department.group.pk,
            'parent_group_id': parent_department and parent_department.group and parent_department.group.pk,
            'status': headcount.status,
            'login': headcount.current_login,
            'job_tickets': [
                {'startrek_key': femida_vacancy.startrek_key, 'status': femida_vacancy.status}
                for femida_vacancy in femida_vacancies
            ],
            'headcount': headcount.headcount,
            'is_intern': current_person and current_person.date_completion_internship is not None,
            'is_maternity': headcount.status == PERSON_POSITION_STATUS.MATERNITY,
            'is_homeworker': current_person and current_person.is_homeworker,
            'category': reward.category if reward else None,
            'join_at': current_person and current_person.join_at,
            'quit_date': dismissal and dismissal.quit_date,
        }

    current_cursor = request.GET.get('cursor')
    if current_cursor is not None and not isinstance(current_cursor, str):
        return HttpResponseBadRequest('Bad cursor value')

    headcounts_page, next_cursor = get_page(queryset=headcounts, cursor=current_cursor)

    headcounts_info = [
        serialize_hc(
            headcount=headcount,
            reward=rewards_map.get(headcount.reward_id),
            femida_vacancies=femida_vacancies_map.get(headcount.code, []),
        )
        for headcount in headcounts_page
    ]

    return JsonResponse(data={'headcounts': headcounts_info, 'cursor': next_cursor})
