import logging
from datetime import timedelta
from typing import Optional

from django.conf import settings
from django.core.cache import caches
from django.db import transaction
from django.utils import timezone

from config import celery_app as app
from lms.courses.models import CourseGroup

from .models import CourseFollower, CourseMailing, MailingEvent
from .services import (
    check_mailing_events, create_mailing_course_available_again, create_mailing_course_enroll_begin,
    create_mailing_new_follower, process_mailing_event,
)
from .settings import (
    MAILING_TASK_COURSE_AVAILABLE_AGAIN, MAILING_TASK_COURSE_ENROLL_BEGIN, MAILING_TASK_COURSE_NEW_FOLLOWER,
)

log = logging.getLogger(__name__)


@app.task(bind=True)
def mailing_course_enroll_begin_task(self, course_id: int, *args, **kwargs) -> None:
    """
    Рассылка о начале регистрации на курс
    """
    try:
        course_mailing = (
            CourseMailing.objects
            .filter(
                course_id=course_id,
                mailing__mailing_task=MAILING_TASK_COURSE_ENROLL_BEGIN,
                is_active=True,
                mailing__is_active=True,
            )
            .select_related('mailing', 'course')
            .first()
        )
        if course_mailing:
            course = course_mailing.course
            if course.available_for_enroll:
                create_mailing_course_enroll_begin(
                    course=course,
                    course_mailing=course_mailing,
                )

    except Exception as exc:
        log.error(exc)
        raise self.retry(exc=exc, max_retries=5, countdown=60)


@app.task(bind=True)
def mailing_course_enroll_begin_periodic_task(self, *args, **kwargs) -> None:
    now = timezone.now()
    start_date = now - timedelta(seconds=settings.COURSE_ENROLL_BEGIN_CHECK_FREQUENCY * 2)
    end_date = now
    courses = (
        CourseGroup.objects
        .available_for_enroll()
        .filter(enroll_begin__gte=start_date, enroll_begin__lte=end_date)
        .values_list('course_id', flat=True)
    )

    for course_id in set(courses):
        key = f'course_enroll_{course_id}'
        if not caches['default'].get(key):
            mailing_course_enroll_begin_task.delay(course_id=course_id)
            if settings.COURSE_ENROLL_THROTTLE_TIME:
                caches['default'].set(key, True, timeout=settings.COURSE_ENROLL_THROTTLE_TIME)


@app.task(bind=True)
def mailing_course_available_again_task(
    self,
    delta_participants: int,
    course_id: int,
    group_id: Optional[int] = None,
    *args, **kwargs
) -> None:
    """
    Рассылка о наличии свободных мест на курсе
    """
    try:
        with transaction.atomic():
            course_mailing = (
                CourseMailing.objects
                .filter(
                    course_id=course_id,
                    mailing__mailing_task=MAILING_TASK_COURSE_AVAILABLE_AGAIN,
                    is_active=True,
                    mailing__is_active=True,
                )
                .select_related('mailing', 'course')
                .first()
            )

            if course_mailing:
                course = course_mailing.course
                if course.available_for_enroll:
                    group = CourseGroup.objects.get(id=group_id) if group_id else None
                    if not group or (group and group.available_for_enroll):
                        create_mailing_course_available_again(
                            course=course,
                            course_mailing=course_mailing,
                            delta_participants=delta_participants,
                        )

    except Exception as exc:
        log.error(exc)
        raise self.retry(exc=exc, max_retries=5, countdown=60)


@app.task(bind=True)
def mailing_course_new_follower_task(self, course_follower_id: int, *args, **kwargs) -> None:
    """
    Рассылка для нового подписчика курса
    """
    try:
        course_follower = (
            CourseFollower.objects
            .filter(id=course_follower_id)
            .select_related('user', 'user__staffprofile', 'course')
            .first()
        )
        if course_follower is None:
            raise CourseFollower.DoesNotExist
        course_mailing = (
            course_follower.course.mailings
            .filter(
                mailing__mailing_task=MAILING_TASK_COURSE_NEW_FOLLOWER,
                is_active=True,
                mailing__is_active=True,
            )
            .select_related('mailing', 'course')
            .first()
        )
        if (
            course_follower.is_active and
            course_mailing is not None
        ):
            create_mailing_new_follower(course_follower=course_follower, course_mailing=course_mailing)
    except Exception as exc:
        log.error(exc)
        raise self.retry(exc=exc, max_retries=5, countdown=60)


@app.task(bind=True)
def process_mailing_event_task(self, mailing_event_id: int):
    try:
        with transaction.atomic():
            mailing_event = (
                MailingEvent.objects
                .filter(id=mailing_event_id)
                .select_for_update(skip_locked=True)
                .select_related('mailing')
                .first()
            )
            if mailing_event is None:
                raise MailingEvent.DoesNotExist
            process_mailing_event(mailing_event=mailing_event)
    except Exception as exc:
        log.error(exc)
        raise self.retry(exc=exc, max_retries=5, countdown=60)


@app.task()
def check_mailing_events_task(batch_limit: Optional[int] = None, time_limit: Optional[int] = None) -> None:
    if batch_limit is None:
        batch_limit = settings.CELERY_BATCH_SIZE_DEFAULT

    check_mailing_events(
        batch_limit=batch_limit,
        time_limit=time_limit,
    )
