from collections import OrderedDict

from django.db import transaction

from intranet.femida.src.interviews.choices import INTERVIEW_ALIVE_STATES
from intranet.femida.src.interviews.models import Assignment
from intranet.femida.src.core.signals import post_update
from intranet.femida.src.problems.models import PresetProblem


def _relink_assignments(source_problem, target_problem):
    """
    Просто меняем ссылку на задачу. Не обращаем внимания на
    """
    queryset = Assignment.objects.filter(problem=source_problem)
    assignment_ids = list(queryset.values_list('id', flat=True))
    queryset.update(problem=target_problem)
    post_update.send(
        sender=Assignment,
        queryset=Assignment.objects.filter(id__in=assignment_ids),
    )


def _relink_preset_problems(source_problem, target_problem):
    """
    Если у пресета уже есть связка с задачей-оригиналом, то связь с копией
    не меняем, а удаляем
    """
    target_problem_preset_ids = set(
        target_problem.presets.values_list('id', flat=True)
    )
    PresetProblem.objects.filter(
        problem=source_problem,
        preset_id__in=target_problem_preset_ids,
    ).delete()

    preset_problems_for_update = (
        PresetProblem.objects
        .filter(problem=source_problem)
        .exclude(preset_id__in=target_problem_preset_ids)
    )
    preset_problems_ids = list(
        preset_problems_for_update.values_list('id', flat=True)
    )
    preset_problems_for_update.update(problem=target_problem)
    post_update.send(
        sender=PresetProblem,
        queryset=PresetProblem.objects.filter(id__in=preset_problems_ids),
    )


def _relink_favorites(source_problem, target_problem):
    fans = list(source_problem.fans.all())
    target_problem.fans.add(*fans)
    source_problem.fans.clear()


def _relink_categories(source_problem, target_problem):
    categories = list(source_problem.categories.all())
    target_problem.categories.add(*categories)


def merge(source_problem, target_problem):
    """
    Производит слияние одной задачи в другую. При этом в таблицах, ссылающихся
    на source_problem, связь меняется на target_problem
    """
    _relink_assignments(source_problem, target_problem)
    _relink_preset_problems(source_problem, target_problem)
    _relink_favorites(source_problem, target_problem)
    _relink_categories(source_problem, target_problem)

    source_problem.deleted = True
    source_problem.original = target_problem
    source_problem.save()


@transaction.atomic
def _reorder(mapping, ids):
    """
    Изменяет порядок линков задач в пресете или собеседовании
    ids: список неких id в нужном порядке
    mapping: упорядоченный маппинг этих id на сущности, порядок которых меняем
    """
    position = 0
    for _id in ids:
        link = mapping.pop(_id, None)
        if link is None:
            continue
        link.position = position
        link.save()
        position += 1

    for position, link in enumerate(mapping.values(), start=position):
        link.position = position
        link.save()


def reorder_preset(preset, problem_ids):
    """
    Изменение порядка задач в пресете
    """
    mapping = OrderedDict(
        (link.problem_id, link) for link in preset.problem_links.all()
    )
    _reorder(mapping, problem_ids)


def reorder_interview(interview, assignment_ids):
    """
    Изменение порядка задач в собеседовании
    """
    mapping = OrderedDict(
        (a.id, a) for a in interview.assignments.all()
    )
    _reorder(mapping, assignment_ids)


def get_problems_usage_in_context(interview=None, preset=None):
    """
    Отдает информацию об использовании задач в том или ином контексте

    :param interview: Interview
    :param preset: Preset
    :return: {
        'interview': {
            'id': <interview_id>,
            'problems': {
                <problem_id>: {
                    'used_for_interview': <bool>,  # Задача использовалась в interview
                    'used_for_another_interview': <bool>,  # Задача использовалась в других
                                                           # секциях кандидата
                },
                ...
            }
        },
        'preset': {
            'id': <preset_id>,
            'problems': {
                <problem_id>: {
                    'used_for_preset': <bool>,
                }
            }
        },
    }
    """
    result = {}
    if interview is not None:
        values_list = (
            Assignment.objects
            .filter(
                problem__deleted=False,
                interview__state__in=INTERVIEW_ALIVE_STATES._db_values,
                interview__candidate=interview.candidate,
            )
            .values_list('problem_id', 'interview_id')
        )
        _map = {}
        for problem_id, interview_id in values_list:
            if problem_id not in _map:
                _map[problem_id] = {
                    'used_for_interview': False,
                    'used_for_another_interview': False,
                }
            if interview_id == interview.id:
                _map[problem_id]['used_for_interview'] = True
            else:
                _map[problem_id]['used_for_another_interview'] = True
        result['interview'] = {
            'id': interview.id,
            'problems': _map,
        }

    if preset is not None:
        values_list = (
            PresetProblem.objects
            .filter(
                problem__deleted=False,
                preset=preset,
            )
            .values_list('problem_id', 'preset_id')
        )
        _map = {}
        for problem_id, preset_id in values_list:
            if problem_id not in _map:
                _map[problem_id] = {
                    'used_for_preset': True,
                }
        result['preset'] = {
            'id': preset.id,
            'problems': _map,
        }

    return result
