import logging

from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import DatabaseError
from django.utils.functional import cached_property
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from intranet.femida.src.actionlog.decorators import action_logged
from intranet.femida.src.api.applications.forms import InternalMessageCreateForm
from intranet.femida.src.api.core.views import (
    BaseView,
    WorkflowViewMixin,
    BaseFormViewMixin,
    InstanceFormViewMixin,
)
from intranet.femida.src.api.core.permissions import IsRecruiterOrAssessor
from intranet.femida.src.candidates.models import Candidate
from intranet.femida.src.candidates.workflow import CandidateWorkflow
from intranet.femida.src.communications.controllers import (
    identify_candidate_from_separator,
    create_message_from_separator,
    MessageAlreadyProcessedError,
)
from intranet.femida.src.communications.choices import (
    MESSAGE_TYPES,
    EXTERNAL_MESSAGE_TYPES,
    MESSAGE_STATUSES,
    MESSAGE_TEMPLATE_TYPES,
)
from intranet.femida.src.communications.helpers import prefetch_user_reminders
from intranet.femida.src.communications.models import Message, MessageTemplate
from intranet.femida.src.communications.workflow import (
    InternalMessageWorkflow,
    NoteWorkflow,
    ExternalMessageWorkflow,
)
from intranet.femida.src.core.shortcuts import get_object_or_40x

from . import serializers, forms
from .permissions import SeparatorPermission

logger = logging.getLogger(__name__)


User = get_user_model()


class CandidateMessageMixin:

    @cached_property
    def candidate(self):
        return get_object_or_40x(Candidate, pk=self.kwargs.get('candidate_id'))

    def get_validator_context(self):
        return {
            'candidate': self.candidate,
        }

    def get_queryset(self):
        # Т.к. при получении кандидата доступы проверяются,
        # для списка сообщений их проверять смысла нет
        return (
            Message.unsafe
            .alive()
            .filter(candidate=self.candidate)
            .select_related(
                'author',
                'candidate',
            )
        )


class CandidateMessageListCreateView(CandidateMessageMixin, BaseFormViewMixin, BaseView):
    """
    Внешняя коммуникация
    """
    model_class = Message
    list_item_serializer_class = serializers.ExternalMessageSerializer
    detail_serializer_class = serializers.ExternalMessageSerializer
    validator_class = forms.ExternalMessageForm

    def filter_queryset(self, queryset):
        queryset = (
            queryset
            .filter(type__in=EXTERNAL_MESSAGE_TYPES._db_values)
            .select_related('application__vacancy')
            .prefetch_related('message_attachments__attachment')
            .order_by('-created')
        )

        return queryset

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    @action_logged('candidate_outcoming_message_create')
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

    def perform_create(self, data):
        workflow = CandidateWorkflow(instance=self.candidate, user=self.request.user)
        return workflow.perform_action('outcoming_message_create', **data)


class MessageDetailViewBase(WorkflowViewMixin, InstanceFormViewMixin, BaseView):

    model_class = Message
    form_serializer_class = serializers.MessageFormSerializer
    validator_class = forms.MessageUpdateForm

    @action_logged('message_update')
    def put(self, request, *args, **kwargs):
        return self.perform_action('update')

    @action_logged('message_delete')
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

    def perform_destroy(self, instance):
        workflow = self.workflow_class(instance=instance, user=self.request.user)
        action = workflow.get_action('delete')
        if not action.is_available():
            raise PermissionDenied('workflow_error')
        return action.perform()


# TODO: унести в претендента в FEMIDA-3828
class InternalMessageDetailView(MessageDetailViewBase):

    detail_serializer_class = serializers.InternalMessageSerializer
    workflow_class = InternalMessageWorkflow


class ExternalMessageDetailView(CandidateMessageMixin, MessageDetailViewBase):

    validator_class = forms.ExternalMessageForm
    detail_serializer_class = serializers.ExternalMessageSerializer
    workflow_class = ExternalMessageWorkflow

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(type__in=EXTERNAL_MESSAGE_TYPES._db_values)

    def lock_object(self):
        """
        Блокирует сообщение. Если оно уже заблокировано, кидает DatabaseError.
        """
        # Note: select_for_update по-умолчанию блокирует все select_related-объекты,
        # поэтому не можем его вставить в get_queryset.
        # Аргумент `of`, позволяющий это контролировать, доступен в Django>=2.0.
        bool(
            Message.unsafe
            .filter(id=self.kwargs['pk'])
            .select_for_update(nowait=True)
            .exists()
        )

    def get_object(self):
        if self.request.method in ['PUT', 'DELETE']:
            # Блокируем scheduled-сообщение от конкурентных изменений в периодической таске.
            try:
                self.lock_object()
            except DatabaseError:
                raise PermissionDenied('Message already taken for sending')
        return super().get_object()


class NoteDetailView(MessageDetailViewBase):

    detail_serializer_class = serializers.NoteSerializer
    workflow_class = NoteWorkflow

    def get_queryset(self):
        """
        Накладываем связь на candidate_id в url и фактический candidate_id в сообщении
        """
        return self.model_class.unsafe.filter(
            candidate_id=self.kwargs['candidate_id'],
        ).exclude(
            status=MESSAGE_STATUSES.deleted,
        )

    @action_logged('candidate_note_update')
    def put(self, request, *args, **kwargs):
        return self.perform_action('update')

    @action_logged('candidate_note_delete')
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)


class NoteListCreateView(CandidateMessageMixin, BaseFormViewMixin, BaseView):

    model_class = Message
    permission_classes = [IsRecruiterOrAssessor]
    list_item_serializer_class = serializers.NoteSerializer
    detail_serializer_class = serializers.NoteSerializer
    validator_class = InternalMessageCreateForm

    def filter_queryset(self, queryset):
        queryset = (
            queryset
            .filter(type=MESSAGE_TYPES.note)
            .select_related('application__vacancy__department')
            .prefetch_related(
                'message_attachments__attachment',
                prefetch_user_reminders(self.request.user),
            )
            .order_by('-created')
        )
        return queryset

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    @action_logged('candidate_note_create')
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

    def perform_create(self, data):
        workflow = CandidateWorkflow(instance=self.candidate, user=self.request.user)
        return workflow.perform_action('note_create', **data)


class MessageTemplatesView(BaseView):

    model_class = MessageTemplate
    list_item_serializer_class = serializers.MessageTemplateSerializer

    def filter_queryset(self, queryset):
        return (
            queryset.filter(
                type__in=(
                    MESSAGE_TEMPLATE_TYPES.communication,
                    MESSAGE_TEMPLATE_TYPES.interview,
                ),
                is_active=True,
            ).order_by('category_name', 'name')
        )

    def get(self, request, *args, **kwargs):
        """
        Список актуальных шаблонов коммуникации
        Отсортированы по category_name, потом по name
        """
        return self.list(request, *args, **kwargs)

    def get_list_item_serializer_context(self):
        # TODO: FEMIDA-5739: удалить if после релиза фронта
        return (
            {'candidate': get_object_or_40x(Candidate, pk=self.kwargs['candidate_id'])}
            if 'candidate_id' in self.kwargs
            else {}
        )


class SeparatorIdentifyCandidateView(APIView):

    permission_classes = (SeparatorPermission,)

    def post(self, request, *args, **kwargs):
        """
        Идентификация application_id от Сепаратора
        """
        appl_id = identify_candidate_from_separator(request.data)
        if appl_id:
            appl_id = str(appl_id)
            logger.info('Identified appl_id from Separator: %s', appl_id)
            return Response(appl_id)

        logger.error(
            'Separator identification failed. Request data: %s', request.data)
        return Response(
            data={'detail': 'Identification failed'},
            status=status.HTTP_404_NOT_FOUND,
        )


class SeparatorCreateMessageView(APIView):

    permission_classes = (SeparatorPermission,)

    @action_logged('candidate_message_create_from_separator')
    def post(self, request, *args, **kwargs):
        """
        Создание входящего сообщения на основе входящего письма кандидата
        """
        log_data = {
            'from': request.data.get('from'),
            'messageId': request.data.get('messageId'),
            'applId': request.data.get('applId'),
            'subject': request.data.get('subject'),
            'inReplyTo': request.data.get('inReplyTo'),
            'references': request.data.get('references'),
        }
        msg_pattern = '[SEPARATOR] %s. Request data: %s'

        try:
            message = create_message_from_separator(request.data)
        except ObjectDoesNotExist:
            reason = 'Object does not exist'
            logger.exception(msg_pattern, reason, log_data)
            return Response(
                data={'detail': reason},
                status=status.HTTP_404_NOT_FOUND,
            )
        except MessageAlreadyProcessedError:
            reason = 'Message already processed'
            logger.error(msg_pattern, reason, log_data)
            return Response(
                data={'detail': reason},
                status=status.HTTP_400_BAD_REQUEST,
            )
        except Exception:
            reason = 'Unhandled exception'
            logger.exception(msg_pattern, reason, log_data)
            return Response(
                data={'detail': reason},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )
        else:
            reason = 'Message %d created' % message.id
            logger.info(msg_pattern, reason, log_data)
            return Response(message.id)
