from django.conf import settings
from django.core.files.base import ContentFile
from django.db.models import Case, When, BooleanField
from django.template import loader

from intranet.femida.src.candidates.choices import (
    CONSIDERATION_STATUSES,
    CONSIDERATION_STATUSES_TRANSLATIONS,
)
from intranet.femida.src.candidates.deduplication import (
    MAYBE_DUPLICATE,
    NOT_DUPLICATE,
    DEFINITELY_DUPLICATE,
)
from intranet.femida.src.candidates.deduplication.shortcuts import get_most_likely_duplicate
from intranet.femida.src.candidates.deduplication.strategies import new_strategy
from intranet.femida.src.candidates.startrek.serializers import EssIssuesFieldsSerializer
from intranet.femida.src.candidates.models import Consideration
from intranet.femida.src.candidates.verifications.helpers import get_previous_ess_issues_keys
from intranet.femida.src.core.serializers import StartrekUniqueField
from intranet.femida.src.notifications import notify
from intranet.femida.src.notifications.utils import get_base_context
from intranet.femida.src.staff.helpers import get_hr_partners
from intranet.femida.src.startrek.operations import IssueTransitionOperation, IssueUpdateOperation
from intranet.femida.src.startrek.utils import create_issue, TransitionEnum, ResolutionEnum
from intranet.femida.src.utils.strings import slugify
from intranet.femida.src.vacancies.choices import VACANCY_ROLES
from intranet.femida.src.vacancies.models import VacancyMembership


def _get_last_consideration(candidate_id):
    # TODO: фильтровать по is_last после релиза FEMIDA-6564
    return (
        Consideration.unsafe
        .filter(candidate_id=candidate_id)
        .annotate(
            is_active=Case(
                When(state=CONSIDERATION_STATUSES.in_progress, then=True),
                default=False,
                output_field=BooleanField(),
            ),
        )
        .order_by(
            '-is_active',  # при наличии активного рассмотрения возьмем его
            '-finished',  # если его нет, возьмем последнее завершенное
        )
        .first()
    )


def _get_duplicate_context(submission):
    context = {
        'MAYBE_DUPLICATE': MAYBE_DUPLICATE,
        'NOT_DUPLICATE': NOT_DUPLICATE,
        'DEFINITELY_DUPLICATE': DEFINITELY_DUPLICATE,
    }
    duplicate, _, decision = get_most_likely_duplicate(
        candidate=submission,
        strategy=new_strategy,
        threshold=MAYBE_DUPLICATE,
    )

    context['duplicate_decision'] = decision
    if decision != DEFINITELY_DUPLICATE:
        return context

    last_consideration = _get_last_consideration(duplicate.id)
    if last_consideration is not None:
        context['last_consideration'] = last_consideration
        # Ответственным за активное рассмотрение являются ответственные за кандидата.
        # При завершении рассмотрения в него снепшотятся ответственные за кандидата
        # на тот момент.
        context['last_consideration_recruiters'] = list(
            last_consideration.responsibles.all()
            if last_consideration.state == CONSIDERATION_STATUSES.archived
            else duplicate.responsibles.all()
        )
    return context


def _get_template_context(**kwargs):
    submission = kwargs['submission']

    context = get_base_context()
    context.update({
        'submission': submission,
        'publications': list(kwargs['reference'].vacancies.published()),
    })
    context.update(_get_duplicate_context(submission))
    return context


def _get_recruiter_usernames(instance):
    """
    Отдаёт логины глав.рекрутеров на вакансиях связанных с `instance`
    :param instance: Reference|Rotation
    """
    return list(
        VacancyMembership.unsafe
        .filter(
            vacancy__in=instance.vacancies.values('id'),
            role=VACANCY_ROLES.main_recruiter,
        )
        .values_list('member__username', flat=True)
        .distinct()
    )


def _get_profession_startrek_ids(instance):
    """
    Отдаёт список id профессий в стартреке, связанных с `instance`
    :param instance: Reference|Rotation
    """
    return list(
        instance.vacancies
        .filter(profession__isnull=False)
        .values_list('profession__startrek_id', flat=True)
        .distinct()
    )


def create_reference_issue(reference):
    submission = reference.submission

    template_name = 'startrek/candidates/reference-created-body.wiki'
    comment_template_name = 'startrek/candidates/reference-created-comment.wiki'
    template_context = _get_template_context(
        submission=submission,
        reference=reference,
    )

    issue_data = {
        'queue': settings.STARTREK_REFERENCE_QUEUE,
        'summary': '{} {}'.format(submission.first_name, submission.last_name),
        'description': loader.render_to_string(template_name, template_context),
        'author': reference.created_by.username,
        'rekrutery': _get_recruiter_usernames(reference),
        'comment': loader.render_to_string(comment_template_name),
        'unique': StartrekUniqueField.generate(reference, settings.STARTREK_REFERENCE_QUEUE),
    }

    profession_startrek_ids = _get_profession_startrek_ids(reference)
    if len(profession_startrek_ids) == 1:
        # Нейминг поля professionalSphere -- это баг в трекере. По факту это справочник профессий.
        issue_data['professionalSphere'] = profession_startrek_ids[0]

    attachment = submission.attachment
    if attachment is not None:
        issue_data['attachments'] = [
            ContentFile(
                content=attachment.attached_file.read(),
                name=slugify(attachment.name),
            ),
        ]

    issue = create_issue(**issue_data)
    reference.startrek_key = issue.key
    reference.save(update_fields=['startrek_key', 'modified'])
    return issue


def create_rotation_issue(rotation, context, **kwargs):
    """
    Создание тикета ROTATION.
    Это тикет, в котором изначально HR-партнёры согласовывают
    возможность ротации сотрудника
    """
    template_name = 'startrek/rotations/create-rotation-issue.wiki'
    context = dict(
        get_base_context(),
        instance=rotation,
        vacancies=list(rotation.vacancies.all()),
        **context
    )
    summary = 'Ротация: {u.first_name} {u.last_name} {u.username}@'.format(u=rotation.created_by)
    issue_data = {
        'queue': settings.STARTREK_ROTATION_QUEUE,
        'summary': summary,
        'description': loader.render_to_string(template_name, context),
        'rekrutery': _get_recruiter_usernames(rotation),
        'followers': kwargs.get('hr_partners', []),
        'employees': kwargs.get('hr_partners', []),
        'unique': StartrekUniqueField.generate(rotation, settings.STARTREK_ROTATION_QUEUE),
        'employee': rotation.created_by.username,
        'assignee': kwargs.get('assignee', None),
    }
    profession_startrek_ids = _get_profession_startrek_ids(rotation)
    if len(profession_startrek_ids) == 1:
        issue_data['professionalSphere'] = profession_startrek_ids[0]

    issue = create_issue(**issue_data)
    rotation.startrek_rotation_key = issue.key
    rotation.save(update_fields=['startrek_rotation_key', 'modified'])
    return issue


def create_myrotation_issue(rotation, context, **kwargs):
    """
    Создание тикета MYROTATION.
    Это тикет, в котором происходит взаимодействие ротирующегося сотрудника
    с HR-партнёрами и рекрутментом
    """
    template_name = 'startrek/rotations/create-myrotation-issue.wiki'
    context = dict(
        get_base_context(),
        instance=rotation,
        vacancies=list(rotation.vacancies.all()),
        **context
    )
    issue = create_issue(
        queue=settings.STARTREK_MYROTATION_QUEUE,
        summary='Ротация: {u.first_name} {u.last_name} {u.username}@'.format(u=rotation.created_by),
        description=loader.render_to_string(template_name, context),
        author=rotation.created_by.username,
        access=kwargs.get('hr_partners', []),
        employee=rotation.created_by.username,
        employees=kwargs.get('hr_partners', []),
        unique=StartrekUniqueField.generate(rotation, settings.STARTREK_MYROTATION_QUEUE),
        assignee=kwargs.get('assignee', None),
    )
    rotation.startrek_myrotation_key = issue.key
    rotation.save(update_fields=['startrek_myrotation_key', 'modified'])
    return issue


def start_rotation(rotation):
    """
    Начало процесса ротации после обработки отклика рекрутером
    """
    recruiter = rotation.submission.candidate.main_recruiter
    context = {
        'user': recruiter,
    }

    # Берём в работу тикет ROTATION
    IssueTransitionOperation(rotation.startrek_rotation_key).delay(
        transition=TransitionEnum.in_progress,
        assignee=recruiter.username,
    )

    # Берём в работу тикет MYROTATION
    IssueTransitionOperation(rotation.startrek_myrotation_key).delay(
        transition=TransitionEnum.in_progress,
        assignee=recruiter.username,
        comment=loader.render_to_string('startrek/rotations/myrotation-started.wiki', context),
        access={'add': _get_recruiter_usernames(rotation)},
    )


def close_rotation(rotation):
    """
    Если ротация закрывается вместе с принятием оффера,
    закрываем тикеты ROTATION и MYROTATION с резолюцией "Решён"
    """
    IssueTransitionOperation(rotation.startrek_rotation_key).delay(
        transition=TransitionEnum.closed,
        resolution=ResolutionEnum.fixed,
        comment=loader.render_to_string('startrek/rotations/rotation-closed.wiki'),
    )
    IssueTransitionOperation(rotation.startrek_myrotation_key).delay(
        transition=TransitionEnum.closed,
        resolution=ResolutionEnum.fixed,
        comment=loader.render_to_string('startrek/rotations/myrotation-closed.wiki'),
    )


def cancel_rotation(rotation):
    """
    Если ротация отменяется, закрываем тикеты ROTATION и MYROTATION
    с резолюцией "Не будет исправлено"
    """
    IssueTransitionOperation(rotation.startrek_rotation_key).delay(
        transition=TransitionEnum.closed,
        resolution=ResolutionEnum.wont_fix,
        comment=loader.render_to_string('startrek/rotations/rotation-canceled.wiki'),
    )
    context = {'hr_partners': get_hr_partners(rotation.created_by.department)}
    IssueTransitionOperation(rotation.startrek_myrotation_key).delay(
        transition=TransitionEnum.closed,
        resolution=ResolutionEnum.wont_fix,
        comment=loader.render_to_string('startrek/rotations/myrotation-canceled.wiki', context),
    )


def create_ess_issue(verification):
    """
    Создание тикета ESS, в который передаем всю необходимую
    информацию по процессу проверки кандидата на конфликт интересов
    """
    template_name = 'startrek/candidates/create-ess-issue.wiki'
    context = dict(
        get_base_context(),
        instance=verification,
    )
    context['previous_ess_issues_keys'] = get_previous_ess_issues_keys(verification.candidate_id)

    issue_fields = EssIssuesFieldsSerializer(verification).data

    issue = create_issue(
        queue=settings.STARTREK_ESS_QUEUE,
        summary='{u.first_name} {u.last_name}'.format(u=verification.candidate),
        description=loader.render_to_string(template_name, context),
        **issue_fields,
    )
    verification.startrek_ess_key = issue.key
    verification.save(update_fields=['startrek_ess_key', 'modified'])
    return issue


def notify_for_candidate_approval(candidate, interviews, challenges, initiator):
    context = get_base_context()
    context['candidate'] = candidate
    context['initiator'] = initiator
    active_interviews = []
    archived_interviews = []
    for interview in interviews:
        if interview.consideration.state == CONSIDERATION_STATUSES.archived:
            archived_interviews.append(interview)
        else:
            active_interviews.append(interview)
    context['interviews'] = active_interviews
    context['archived_interviews'] = archived_interviews
    active_challenges = []
    archived_challenges = []
    for challenge in challenges:
        if challenge.consideration.state == CONSIDERATION_STATUSES.archived:
            archived_challenges.append(challenge)
        else:
            active_challenges.append(challenge)
    context['challenges'] = active_challenges
    context['archived_challenges'] = archived_challenges

    notify(
        transport='startrek',
        template_name='startrek/candidates/complete.wiki',
        context=context,
        key=candidate.startrek_key,
    )


def create_onedayoffer_issue(challenge):
    """
    Создание тикета ONEDAYOFFER, в который передаем
    информацию по кандидату, откликнувшемуся на OneDayOffer
    """
    submission = challenge.submission
    form_data = submission.candidate_data
    profession = form_data.get('cand_profession', '').strip()
    skills = form_data.get('cand_skills', '').strip()
    queue = form_data.get('fasttrack_queue', '').upper()
    email = submission.email.strip()

    template_name = 'startrek/candidates/create-one-day-offer-issue.wiki'
    template_summary_name = 'startrek/candidates/one-day-offer-issue-summary.wiki'
    context = dict(
        get_base_context(),
        submission=submission,
        profession=profession,
        skills=skills,
        email=email,
    )

    tags = {skill.strip() for skill in skills.split(',')}
    tags.add(profession)
    for key in form_data:
        if key.startswith('utm_'):
            tags.add(form_data[key])

    issue = create_issue(
        queue=queue or settings.STARTREK_ONEDAYOFFER_QUEUE,
        summary=loader.render_to_string(template_summary_name, context),
        description=loader.render_to_string(template_name, context),
        unique=StartrekUniqueField.generate(challenge, settings.STARTREK_ONEDAYOFFER_QUEUE),
        tags=[tag for tag in tags if tag],
        candidateEmail=email,
    )
    challenge.startrek_onedayoffer_key = issue.key
    challenge.save(update_fields=['startrek_onedayoffer_key'])


def send_onedayoffer_event(challenges, event):
    """
    Транслируем в ONEDAYOFFER-тикеты изменение статуса кандидата
    """
    comment = f'Изменился статус кандидата: {CONSIDERATION_STATUSES_TRANSLATIONS[event]}'
    issue_params = {
        'comment': comment,
        'tags': {'add': [event]},
    }
    without_issue = [c.id for c in challenges if not c.startrek_onedayoffer_key]
    if without_issue:
        raise Exception(f'Challenges without startrek_onedayoffer_key: {without_issue}')
    for challenge in challenges:
        operation = IssueUpdateOperation(challenge.startrek_onedayoffer_key)
        operation.delay(**issue_params)


def send_onedayoffer_challenge_results(challenge):
    """
    В ONEDAYOFFER-тикет сообщаем результаты испытания в Контесте
    """
    if not challenge.startrek_onedayoffer_key:
        raise Exception(f'Challenge {challenge.id} does not have startrek_onedayoffer_key')
    temlate_name = 'startrek/candidates/complete-challenges-part.wiki'
    context = dict(
        get_base_context(),
        challenges=[challenge],
    )
    issue_params = {
        'comment': loader.render_to_string(temlate_name, context),
        'tags': {'add': ['challenge_' + challenge.resolution]},
    }
    operation = IssueUpdateOperation(challenge.startrek_onedayoffer_key)
    operation.delay(**issue_params)
