from model_utils import FieldTracker
from model_utils.models import TimeStampedModel
from simple_history.models import HistoricalRecords

from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from lms.courses.models import CourseFile, CourseModule, CourseStudent

from .validators import (
    validate_scorm_max_attempts, validate_student_resource_attempt_course, validate_student_scorm_course,
)


def max_attempts_default():
    return 1 if settings.SCORM_DEFAULT_MAX_ATTEMPTS < 0 else settings.SCORM_DEFAULT_MAX_ATTEMPTS


class Scorm(CourseModule):
    current_file = models.ForeignKey(
        'ScormFile',
        verbose_name=_("текущая версия файла"),
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='scorm_module',
    )
    max_attempts = models.PositiveSmallIntegerField(
        _("максимальное количество попыток"),
        default=max_attempts_default,
        help_text=_("максимальное количество попыток прохождения scorm-курса студентом (0 - неограничено)"),
    )

    class Meta:
        ordering = ('order',)
        verbose_name = _("SCORM")
        verbose_name_plural = _("SCORM")

    def clean(self):
        super().clean()
        if self.is_active and (
            not self.current_file or self.current_file.scorm_status != ScormFile.SCORM_MODULE_STATUS_READY
        ):
            raise ValidationError({
                'is_active': ValidationError(
                    _("Нельзя опубликовать модуль, пока не загружен scorm-архив"),
                    code='invalid',
                )
            })

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        if self.students_attempts.exists():
            raise ValidationError(_("Нельзя удалить модуль, в котором есть попытки прохождения"), code='invalid')
        return super().delete(*args, **kwargs)


class ScormFile(TimeStampedModel):
    scorm = models.ForeignKey(
        Scorm,
        verbose_name=_("scorm"),
        on_delete=models.CASCADE,
        related_name='files',
        help_text=_("scorm-курс, к которому относится данный файл"),
    )
    course_file = models.OneToOneField(
        CourseFile,
        verbose_name=_("файл курса"),
        on_delete=models.CASCADE,
        related_name='scorm_file',
    )
    public_url = models.CharField(
        _("распакованный курс"),
        max_length=255,
        blank=True,
        help_text=_('путь к папке с распакованным scorm-курсом'),
    )

    SCORM_MODULE_STATUS_PENDING = 'pending'
    SCORM_MODULE_STATUS_READY = 'ready'
    SCORM_MODULE_STATUS_ERROR = 'error'

    SCORM_MODULE_STATUS_CHOICES = (
        (SCORM_MODULE_STATUS_PENDING, _("в обработке")),
        (SCORM_MODULE_STATUS_READY, _("готов")),
        (SCORM_MODULE_STATUS_ERROR, _("ошибка")),
    )

    scorm_status = models.CharField(
        _("статус"),
        max_length=10,
        choices=SCORM_MODULE_STATUS_CHOICES,
        default=SCORM_MODULE_STATUS_PENDING,
        help_text=_("статус распаковки архива с курсом и готовности к использованию"),
    )
    error_messages = models.TextField(
        _("ошибки распаковки архива и разбора манифеста"),
        blank=True,
    )
    manifest = JSONField(
        _("манифест"),
        default=dict,
        blank=True,
        help_text=_("разобранный xml-манифест scorm-курса (imsmanifest.xml)"),
    )
    comment = models.TextField(
        _("комментарий"),
        blank=True,
        help_text=_("что изменилось в новой версии"),
    )

    tracker = FieldTracker(fields=['scorm_status'])

    class Meta:
        verbose_name = _("SCORM файл")
        verbose_name_plural = _("SCORM файлы")

    def clean(self):
        super().clean()
        if self.course_file.course_id != self.scorm.course_id:
            raise ValidationError(
                _("Нельзя добавлять файл из другого курса"),
                code='invalid',
            )

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)


class ScormResource(TimeStampedModel):
    scorm_file = models.ForeignKey(
        ScormFile,
        verbose_name=_("scorm-файл"),
        on_delete=models.CASCADE,
        related_name='resources',
    )
    resource_id = models.CharField(
        _("идентификатор ресурса"),
        max_length=255,
    )
    href = models.CharField(
        _("путь к ресурсу"),
        max_length=255,
        help_text=_("относительный путь к точке входа в ресурс в папке со scorm-курсом"),
    )

    class Meta:
        verbose_name = _("SCORM-ресурс")
        verbose_name_plural = _("SCORM-ресурсы")
        unique_together = (
            ('scorm_file', 'resource_id'),
        )

    def __str__(self):
        return f'{self.resource_id} | {self.scorm_file}'

    def delete(self, *args, **kwargs):
        if self.students_resource_attempts.exists():
            raise ValidationError(_("Нельзя удалить scorm-ресурс, в котором есть попытки"), code='invalid')
        return super().delete(*args, **kwargs)


class ScormStudentAttempt(TimeStampedModel):
    scorm = models.ForeignKey(
        Scorm,
        verbose_name=_("scorm-курс"),
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name='students_attempts',
    )
    scorm_file = models.ForeignKey(
        ScormFile,
        verbose_name=_("scorm-файл"),
        on_delete=models.PROTECT,
    )
    student = models.ForeignKey(
        CourseStudent,
        verbose_name=_("студент"),
        on_delete=models.PROTECT,
        related_name='scorm_attempts',
    )
    current_attempt = models.PositiveSmallIntegerField(
        verbose_name=_("номер текущей попытки"),
        default=0,
    )

    tracker = FieldTracker(fields=['current_attempt'])

    history = HistoricalRecords()

    class Meta:
        verbose_name = _("Попытка прохождения Scorm-курса")
        verbose_name_plural = _("Попытки прохождения Scorm-курса")
        unique_together = (
            ('scorm', 'student'),
        )

    def clean(self):
        errors = {}
        try:
            validate_student_scorm_course(scorm=self.scorm, student=self.student)
            validate_scorm_max_attempts(attempt=self)
        except ValidationError as exc:
            errors = exc.update_error_dict(errors)

        if errors:
            raise ValidationError(errors)

    def save(self, *args, **kwargs):
        self.current_attempt += 1
        self.full_clean()
        if not self.scorm:
            self.scorm = self.scorm_file.scorm
        return super().save(*args, **kwargs)

    def __str__(self):
        return f'Scorm: {self.scorm_id}| student: {self.student_id}'


class ScormResourceStudentAttempt(TimeStampedModel):
    student = models.ForeignKey(
        CourseStudent,
        verbose_name=_("студент"),
        on_delete=models.PROTECT,
        related_name='student_scorm_resource_attempts',
    )
    scorm_resource = models.ForeignKey(
        ScormResource,
        verbose_name=_("scorm-ресурс"),
        on_delete=models.PROTECT,
        related_name='students_resource_attempts',
    )
    current_attempt = models.PositiveSmallIntegerField(
        verbose_name=_("номер текущей попытки"),
        default=1,
    )
    data = JSONField(
        _("данные прохождения"),
        default=dict,
        blank=True,
        help_text=_("данные прохождения пользователем scorm-ресурса"),
    )
    scaled_score = models.DecimalField(
        verbose_name=_("Балл, набранный студентом"),
        help_text=_("Балл студента в диапазоне -1.0...1.0"),
        max_digits=10,
        decimal_places=7,
        null=True,
        blank=True,
    )

    class Meta:
        verbose_name = _("Попытка прохождения Scorm-ресурса")
        verbose_name_plural = _("Попытки прохождения Scorm-ресурса")
        unique_together = (
            ('student', 'scorm_resource', 'current_attempt'),
        )

    def clean(self):
        validate_student_resource_attempt_course(student=self.student, scorm_resource=self.scorm_resource)

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

    def __str__(self):
        return f'Scorm resource: {self.scorm_resource_id}| student: {self.student_id}'
