import logging

from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils import timezone

from intranet.femida.src.celery_app import app
from intranet.femida.src.communications.choices import (
    MESSAGE_STATUSES,
    REMINDER_STATUSES,
    MESSAGE_TYPES,
)
from intranet.femida.src.communications.models import Message, Reminder
from intranet.femida.src.notifications.communications import (
    ExternalMessageCreatedNotification,
    ExternalMessageFailedNotification,
)
from intranet.femida.src.notifications.reminders import (
    InternalMessageReminderNotification,
    NoteReminderNotification,
)
from intranet.femida.src.utils.lock import locked_task


User = get_user_model()

logger = logging.getLogger(__name__)


# TODO: удалить после релиза FEMIDA-7062 и включения свитча `enable_message_status_failed`
@app.autoretry_task(max_retries=3)
def mark_message_sent_and_notify_task(message_id):
    set_message_status_and_notify_task(None, message_id)


@app.autoretry_task(max_retries=3)
def set_message_status_and_notify_task(error, message_id):
    message = (
        Message.unsafe
        .select_related(
            'author',
            'candidate',
        )
        .get(id=message_id)
    )

    if error:
        message.status = MESSAGE_STATUSES.failed
        message.error = error
        notification_class = ExternalMessageFailedNotification
    else:
        message.status = MESSAGE_STATUSES.sent
        notification_class = ExternalMessageCreatedNotification

    message.save(update_fields=['status', 'error'])

    notification = notification_class(message, message.author)
    notification.send()


@app.task
@locked_task
def send_scheduled_emails_task():
    """
    Отправляет все письма, отложенные на прошедшее время.
    """
    from intranet.femida.src.communications.controllers import send_email_to_candidate

    # 1. Берем не заблокированные другими тасками сообщения и блокируем их.
    # 2. Переводим их в статус `sending`. При нем другие таски эти письма не возьмут.
    # 3. Отпускаем блокировку.
    # 4. Отправляем только те сообщения в статусе `sending`, которые мы взяли в этой таске.
    #    В статусе `sending` сообщения могут так же появиться в результате
    #    параллельной работы другой send_scheduled_emails_task
    #    или при создании пользователем не отложенного сообщения.
    with transaction.atomic():
        messages = (
            Message.unsafe
            .filter(
                status=MESSAGE_STATUSES.scheduled,
                schedule_time__lte=timezone.now(),
            )
            .exclude(status=MESSAGE_STATUSES.deleted)
            .prefetch_related('message_attachments')
            .select_for_update(skip_locked=True)
        )
        message_ids = [m.id for m in messages]
        Message.unsafe.filter(id__in=message_ids).update(status=MESSAGE_STATUSES.sending)

    for message in messages:
        attachment_ids = [i.attachment_id for i in message.message_attachments.all()]
        send_email_to_candidate(message, attachment_ids)


@app.task
@locked_task
def send_reminders_emails_task():
    """
    Отправляет напоминания, сработавшие за прошедший период.
    """
    reminders = (
        Reminder.objects
        .filter(
            status=REMINDER_STATUSES.scheduled,
            remind_at__lte=timezone.now(),
        )
        .select_related(
            'user',
            'message__candidate',
            'message__application',
        )
        .prefetch_related(
            'message__attachments',
            'message__candidate__target_cities',
            'message__candidate__candidate_skills__skill',
            'message__candidate__candidate_professions__profession',
            'message__candidate__responsibles',
            'message__application__vacancy__profession',
            'message__application__vacancy__cities',
            'message__application__vacancy__skills',
        )
    )

    with transaction.atomic():
        reminders_ids = [r.id for r in reminders]
        Reminder.objects.filter(id__in=reminders_ids).update(status=REMINDER_STATUSES.sending)

    for reminder in reminders:
        with transaction.atomic():
            if reminder.message.type == MESSAGE_TYPES.note:
                notification = NoteReminderNotification(reminder)
                notification.send()
            elif reminder.message.type == MESSAGE_TYPES.internal:
                notification = InternalMessageReminderNotification(reminder)
                notification.send()
            else:
                logger.warning(
                    'Cannot send reminder %d. Unsupported message type %s',
                    reminder.id,
                    reminder.message.type,
                )
                continue

            reminder.status = REMINDER_STATUSES.sent
            reminder.save(update_fields=['status'])
