from constance import config
from django.utils import timezone
from django.utils.functional import cached_property
from django.conf import settings

from intranet.femida.src.applications.helpers import active_applications_query
from intranet.femida.src.calendar.tasks import replace_event_attendee_task
from intranet.femida.src.candidates.helpers import blank_modify_candidate
from intranet.femida.src.candidates.considerations.controllers import (
    update_consideration_extended_status,
)
from intranet.femida.src.comments.controllers import update_or_create_comment
from intranet.femida.src.core.signals import post_update
from intranet.femida.src.core.workflow import Workflow, Action
from intranet.femida.src.interviews.controllers import update_interview
from intranet.femida.src.interviews.helpers import (
    InterviewVisibilityHelper,
    get_review_issue_assignee,
    is_interview_review_needed,
)
from intranet.femida.src.interviews.models import Assignment, Interview
from intranet.femida.src.interviews.signals import interview_cancelled, interview_finished
from intranet.femida.src.interviews.startrek.issues import create_review_issue, update_review_issue
from intranet.femida.src.interviews.tasks import create_review_issue_task
from intranet.femida.src.notifications.candidates import notify_all_interviews_completed
from intranet.femida.src.notifications.interviews import (
    notify_about_interview_estimate,
    InterviewCancelledNotification,
    InterviewReassignedNotification,
    InterviewFinishedNotification,
)
from intranet.femida.src.problems.controllers import add_problems_from_preset
from intranet.femida.src.staff.tasks import give_interview_counter_achievement
from intranet.femida.src.startrek.utils import StartrekError
from intranet.femida.src.utils.strings import fetch_comma_separated_integers

from . import choices


class InterviewBaseAction(Action):

    status_field_name = 'state'
    valid_types = None

    def is_visible(self):
        return self.is_status_correct() and self.is_type_valid() and self.has_permission()

    def is_type_valid(self):
        return self.instance.type in self.valid_types if self.valid_types is not None else True

    def has_permission(self):
        return self.user.is_recruiter or self.instance.interviewer == self.user

    def blank_modify_candidate(self):
        """
        Обновляет поля modified у кандидата
        и активных претенденств, соответствующих секции
        """
        now = timezone.now()
        self.instance.candidate.applications.filter(active_applications_query).update(modified=now)
        blank_modify_candidate(self.instance.candidate)

    def complete_consideration_if_possible(self):
        """
        При завершении всех секций рассылаем два типа уведомлений:
        1. Завершены все gradable секции, отбивка всем участникам процесса;
        2. По АА-секции поставили hire, по любой другой - no hire, отбивка Фокину.
        """
        interviews = (
            self.instance.consideration.interviews
            .alive()
            .select_related('interviewer')
        )

        interviews_states = set(i.state for i in interviews)
        if interviews_states != {Interview.STATES.finished}:
            return

        notify_all_interviews_completed(
            interview=self.instance,
            initiator=self.user,
            interviews=interviews,
        )


class CreateReviewIssueMixin:
    def _create_review_issue(self, initiator=None, comment=None):
        queue = None

        if self.instance.is_screening or self.instance.is_regular:
            queue = settings.STARTREK_SCREENING_REVIEW_QUEUE
        elif self.instance.is_aa:
            queue = settings.AA_TYPE_TO_REVIEW_QUEUE.get(self.instance.aa_type)

        assert queue is not None

        assignee = get_review_issue_assignee(
            interview=self.instance,
            initiator=initiator,
        )
        try:
            create_review_issue(
                interview=self.instance,
                queue=queue,
                initiator=initiator,
                assignee=assignee,
                comment=comment,
            )
        except StartrekError as exc:
            if initiator:
                self.raise_error(exc.message)
            else:
                create_review_issue_task.delay(
                    interview_id=self.instance.id,
                    queue=queue,
                    initiator_id=initiator.id if initiator else None,
                    assignee=assignee,
                )


class ReassignAction(InterviewBaseAction):

    valid_statuses = (
        Interview.STATES.assigned,
    )

    def perform(self, **params):
        reason = params['reason']
        old_interviewer = self.instance.interviewer
        self.instance.interviewer = params['interviewer']
        self.instance.save()

        if old_interviewer != self.instance.interviewer:
            if self.instance.event_id:
                replace_event_attendee_task.delay(
                    event_id=self.instance.event_id,
                    old_attendee_email=old_interviewer.email,
                    new_attendee_email=self.instance.interviewer.email,
                )
            notification = InterviewReassignedNotification(
                instance=self.instance,
                initiator=self.user,
                old_interviewer=old_interviewer,
                reason=reason,
            )
            notification.send()

        return self.instance


class EstimateAction(InterviewBaseAction):

    valid_types = choices.INTERVIEW_GRADABLE_TYPES
    valid_statuses = (
        Interview.STATES.assigned,
    )

    def has_permission(self):
        return super().has_permission() and self.instance.grade is None

    def perform(self, **params):
        self.instance.grade = params.get('grade')
        self.instance.state = Interview.STATES.estimated
        self.instance.save()
        self.blank_modify_candidate()
        notify_about_interview_estimate(interview=self.instance)
        return self.instance


class CancelAction(InterviewBaseAction):

    valid_statuses = (
        Interview.STATES.assigned,
    )

    def perform(self, **params):
        for attr, value in params.items():
            setattr(self.instance, attr, value)

        self.instance.assignments.all().delete()
        self.instance.state = Interview.STATES.cancelled
        self.instance.save()
        update_consideration_extended_status(self.instance.consideration)

        notification = InterviewCancelledNotification(self.instance, self.user)
        notification.send()
        interview_cancelled.send(
            sender=Interview,
            interview=self.instance,
            initiator=self.user,
        )

        self.complete_consideration_if_possible()
        return self.instance


class FinishAction(CreateReviewIssueMixin, InterviewBaseAction):

    def has_permission_for_gradable(self):
        return (
            self.instance.is_gradable
            and self.instance.state == Interview.STATES.estimated
        )

    def has_permission_for_non_gradable(self):
        return (
            not self.instance.is_gradable
            and self.instance.state == Interview.STATES.assigned
        )

    def has_permission(self):
        return (
            super().has_permission()
            and (
                self.has_permission_for_gradable()
                or self.has_permission_for_non_gradable()
            )
        )

    def perform(self, **params):
        self.instance.finished = timezone.now()
        self.instance.finished_by = self.user
        self.instance.state = Interview.STATES.finished
        self.instance.save()
        self.complete_consideration_if_possible()
        update_consideration_extended_status(self.instance.consideration)
        self.blank_modify_candidate()

        notification = InterviewFinishedNotification(self.instance, self.user)
        notification.send()
        interview_finished.send(
            sender=Interview,
            interview=self.instance,
            initiator=self.user,
        )

        if is_interview_review_needed(self.instance):
            self._create_review_issue()

        if self.instance.is_gradable:
            # Будем проверять соответствие ачивки при каждом завершении
            # С одной стороны это немного избыточно,
            # с другой стороны, менее вероятны расхождения
            give_interview_counter_achievement.delay(login=self.instance.interviewer.username)

        return self.instance


class UpdateAction(InterviewBaseAction):

    valid_statuses = (
        Interview.STATES.assigned,
    )

    def has_permission(self):
        return self.user.is_recruiter

    def perform(self, **params):
        return update_interview(self.instance, params, self.user)


class PartialUpdateAction(InterviewBaseAction):

    valid_statuses = (
        Interview.STATES.assigned,
        Interview.STATES.estimated,
    )

    def perform(self, **params):
        return update_interview(self.instance, params, self.user)


class RenameAction(InterviewBaseAction):

    def perform(self, **params):
        return update_interview(self.instance, params, self.user)


class CommentAddAction(InterviewBaseAction):

    valid_statuses = (
        Interview.STATES.finished,
    )

    def has_permission(self):
        return self.wf.visibility_helper.is_visible(self.instance)

    def perform(self, **params):
        params['related_object'] = self.instance
        return update_or_create_comment(params, self.user)


class AddProblemsFromPresetAction(InterviewBaseAction):

    valid_statuses = (
        Interview.STATES.assigned,
        Interview.STATES.estimated,
    )

    def perform(self, **params):
        add_problems_from_preset(
            link_model_class=Assignment,
            preset=params['preset'],
            target=self.instance,
            target_field_name='interview',
        )
        return self.instance


class SendToReviewAction(CreateReviewIssueMixin, InterviewBaseAction):

    valid_types = (
        choices.INTERVIEW_TYPES.screening,
        choices.INTERVIEW_TYPES.regular,
        choices.INTERVIEW_TYPES.aa,
    )
    valid_statuses = (
        Interview.STATES.finished,
    )

    def has_permission(self):
        return (
            self.wf.visibility_helper.is_visible(self.instance)
            and self._is_professional_sphere_allowed()
        )

    def perform(self, **params):
        if self.instance.startrek_review_key:
            update_review_issue(
                interview=self.instance,
                initiator=self.user,
                comment=params['comment'],
            )
        else:
            self._create_review_issue(
                initiator=self.user,
                comment=params.get('comment'),
            )

        return self.instance

    def _is_professional_sphere_allowed(self):
        if self.instance.is_aa_canonical:
            return self.user.is_aa_canonical
        if self.instance.is_aa_mlp:
            return self.user.is_aa_mlp
        if self.instance.is_screening or self.instance.is_regular:
            sphere_ids = fetch_comma_separated_integers(config.INTERVIEW_REVIEW_PROF_SPHERE_IDS)
            return self.instance.application.vacancy.professional_sphere_id in sphere_ids
        return False


class InterviewWorkflow(Workflow):

    ACTION_MAP = {
        'reassign': ReassignAction,
        'estimate': EstimateAction,
        'comment': PartialUpdateAction,
        'cancel': CancelAction,
        'finish': FinishAction,
        'update': UpdateAction,
        'rename': RenameAction,
        'change_resolution': PartialUpdateAction,
        'comment_add': CommentAddAction,
        'add_problems_from_preset': AddProblemsFromPresetAction,
        'send_to_review': SendToReviewAction,
        'change_grade': PartialUpdateAction,
        'choose_scale': PartialUpdateAction,
    }

    @cached_property
    def visibility_helper(self):
        return InterviewVisibilityHelper(self.user, self.instance.consideration)

    def get_actions_visibility(self):
        result = super().get_actions_visibility()
        extra_actions_enabled = (
            self.instance.state in (Interview.STATES.assigned, Interview.STATES.estimated)
        )
        result['reorder'] = extra_actions_enabled
        result['assignment_create'] = extra_actions_enabled
        return result


class InterviewRoundCancelAction(Action):

    valid_statuses = (
        choices.INTERVIEW_ROUND_STATUSES.new,
        choices.INTERVIEW_ROUND_STATUSES.planning,
    )

    def has_permission(self):
        return self.user.is_recruiter

    def perform(self, **params):
        interviews = self.instance.interviews.all()
        interviews.update(state=Interview.STATES.cancelled, modified=timezone.now())
        post_update.send(sender=Interview, queryset=interviews)

        self.instance.status = choices.INTERVIEW_ROUND_STATUSES.cancelled
        self.instance.save()
        return self.instance


class InterviewRoundWorkflow(Workflow):

    ACTION_MAP = {
        'cancel': InterviewRoundCancelAction,
    }
