from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import QuerySet
from django.db.models.aggregates import Sum, Avg
from django.db.models.expressions import Case, When, F
from django.db.models.fields import IntegerField
from django.db.models.manager import Manager
from django.template.defaultfilters import slugify
from django.utils.translation import gettext as _
from django_extensions.db.models import TimeStampedModel
from embed_video.fields import EmbedVideoField
from exercises.models import Exercise
from mailing.mail_service import EmailSender
from popojutrze import settings
from tasks.constants import ApprenticeTaskAnswerStatus
from utils.general import normalize_polish_chars, get_base_url_from_request


class PathManager(models.Manager):
    def active(self):
        return self.all().order_by('position')


class Path(models.Model):
    name = models.CharField(max_length=70, verbose_name=_('nazwa'))
    is_path_blocking = models.BooleanField(default=True, verbose_name=_('czy blokująca?'))
    image = models.ImageField(null=True, blank=True, upload_to='path_images/', verbose_name=_('grafika'))
    position = models.PositiveIntegerField(default=0, blank=True, verbose_name=_('pozycja'))

    objects = PathManager()

    def slug(self):
        return slugify(normalize_polish_chars(self.name))

    class Meta:
        ordering = ('position',)
        verbose_name = _('ścieżka')
        verbose_name_plural = _('ścieżki')

    def __str__(self):
        return '{}'.format(self.name, )


class TaskQuerySet(QuerySet):
    def active(self):
        return self.filter(is_active=True).order_by('position')


class TaskManager(Manager.from_queryset(TaskQuerySet)):
    pass


class Task(models.Model):
    path = models.ForeignKey('Path', related_name='tasks', verbose_name=_('ścieżka'))
    is_active = models.BooleanField(default=True, verbose_name=_('czy aktywne?'))
    title = models.CharField(blank=True, max_length=70, verbose_name=_('tytuł'))
    subtitle = models.CharField(blank=True, max_length=70, verbose_name=_('podtytuł'))
    header = models.CharField(blank=True, max_length=70, verbose_name=_('nagłówek'))
    image = models.ImageField(null=True, blank=True, upload_to='path_images/', verbose_name=_('grafika'))
    yt_video = EmbedVideoField(blank=True, null=True, verbose_name=_('video'))
    description = models.TextField(blank=True, verbose_name=_('opis'))
    instruction = models.TextField(blank=True, verbose_name=_('instrukcja'))
    articles = models.ManyToManyField('articles.Article', related_name='tasks', blank=True, verbose_name=_('artykuły'))
    mark_criteria = models.ManyToManyField('marks.MarkCriteria', related_name='tasks', through='TaskMarkCriteria',
                                           blank=True, verbose_name=_('kryteria_oceny'))
    exercises = models.ManyToManyField('exercises.Exercise', through='ExerciseTaskPosition', blank=True,
                                       related_name='tasks', verbose_name=_('ćwiczenia'))
    position = models.PositiveIntegerField(default=0, blank=True, verbose_name=_('pozycja'))

    objects = TaskManager()

    class Meta:
        ordering = ('position',)
        verbose_name = _('zadanie')
        verbose_name_plural = _('zadania')

    def slug(self):
        return slugify(normalize_polish_chars(self.title))

    def get_in_path_position(self):
        return self.path.tasks.active().filter(position__lte=self.position).count()

    def all_exercises_count(self):
        return Exercise.objects.from_task_count(self)

    def done_exercises_count(self, apprentice):
        return Exercise.objects.from_task(self).annotated_with_done_by_apprentice_count(apprentice)

    def all_exercises_done(self, apprentice):
        return self.all_exercises_count() == self.done_exercises_count(apprentice)

    def __str__(self):
        return '{} - {}'.format(self.title, self.path)


class ExerciseTaskPositionManager(models.Manager):
    def active(self):
        return self.filter(exercise__is_active=True).order_by('position')

    def annotated_with_done_by_apprentice(self, apprentice):
        return self.active().annotate(
            done=Sum(Case(When(exercise__apprentices=apprentice, then=1), default=0,
                          output_field=IntegerField())))


class ExerciseTaskPosition(models.Model):
    exercise = models.ForeignKey('exercises.Exercise', related_name='exercise_task_positions',
                                 verbose_name=_('cwiczenie'))
    task = models.ForeignKey('tasks.Task', related_name='exercise_task_positions', verbose_name=_('zadanie'))
    position = models.PositiveIntegerField(default=0, blank=True, verbose_name=_('pozycja'))

    objects = ExerciseTaskPositionManager()

    class Meta:
        ordering = ('position',)
        verbose_name = _('ćwiczenie w zadaniu')
        verbose_name_plural = _('ćwiczenia w zadaniach')

    def next(self):
        return ExerciseTaskPosition.objects.active().filter(position__gt=self.position, task=self.task).order_by(
            'position').first()

    def previous(self):
        return ExerciseTaskPosition.objects.active().filter(position__lt=self.position, task=self.task).order_by(
            '-position').first()

    def __str__(self):
        return '{} - {}: {}'.format(self.task, self.exercise, self.position)


class TaskMarkCriteriaManager(models.Manager):
    # Założenie jest takie, że traktujemy ten apprentice_video jako video_2 w task_answer
    def from_apprentice_video_with_likes(self, apprentice_video):
        return apprentice_video.task_answer_as_2.task.task_mark_criteria.all().annotate(likes=Sum(
            Case(When(apprentice_scores__apprentice_video=apprentice_video, then=1), defult=0,
                 output_field=IntegerField())))


class TaskMarkCriteria(models.Model):
    task = models.ForeignKey('Task', related_name='task_mark_criteria', verbose_name=_('zadanie'))
    mark_criteria = models.ForeignKey('marks.MarkCriteria', related_name='task_mark_criteria',
                                      verbose_name=_('kryterium oceny'))

    objects = TaskMarkCriteriaManager()

    class Meta:
        verbose_name = _('kryterium oceny w zadaniu')
        verbose_name_plural = _('kryteraia ocen w zdaniach')

    def __str__(self):
        return '{} in {} '.format(self.mark_criteria, self.task)


class MentorTaskMarkCriteriaApprenticeVideoValueQuerySet(QuerySet):
    def group_averages(self):
        return self.values('task_mark_criteria__mark_criteria__group__name').annotate(group_name=F(
            'task_mark_criteria__mark_criteria__group__name')).annotate(group_avg=Avg('value'))

    def average(self):
        return self.aggregate(average=Avg('value')).get('average')


class MentorTaskMarkCriteriaApprenticeVideoValueManager(
    Manager.from_queryset(MentorTaskMarkCriteriaApprenticeVideoValueQuerySet)):
    pass


class MentorTaskMarkCriteriaApprenticeVideoValue(TimeStampedModel):
    mentor = models.ForeignKey('users.Mentor', related_name='mentor_scores', verbose_name=_('mentor'))
    apprentice_video = models.ForeignKey('ApprenticeVideo', related_name='mentor_scores',
                                         verbose_name=_('video do zadania'))
    task_mark_criteria = models.ForeignKey('TaskMarkCriteria', related_name='mentor_scores',
                                           verbose_name=_('kryterium oceny'))
    value = models.FloatField(verbose_name=_('wartość'),
                              validators=[MaxValueValidator(10.0), MinValueValidator(0.0)])

    objects = MentorTaskMarkCriteriaApprenticeVideoValueManager()

    class Meta:
        verbose_name = _('ocena filmu przez mentora w kryterium')
        verbose_name_plural = _('oceny filmu przez mentora w kryterium')

    def __str__(self):
        return '{} - {} / {} / {}'.format(self.mentor, self.apprentice_video, self.task_mark_criteria, self.value)


class MentorApprenticeVideoOpinion(TimeStampedModel):
    mentor = models.ForeignKey('users.Mentor', related_name='mentor_text_score', verbose_name=_('mentor'))
    apprentice_video = models.OneToOneField('ApprenticeVideo', related_name='mentor_text_score',
                                            verbose_name=_('video do zadania'))
    opinion = models.TextField(verbose_name=_('opinia'))

    class Meta:
        verbose_name = _('ocena tekstowa filmu przez mentora')
        verbose_name_plural = _('oceny tekstowe filmów przez mentora')

    def __str__(self):
        return '{} - {} / {}'.format(self.mentor, self.apprentice_video, self.opinion)


class ApprenticeTaskMarkCriteriaApprenticeVideo(TimeStampedModel):
    apprentice = models.ForeignKey('users.Apprentice', related_name='apprentice_scores', verbose_name=_('uczeń'))
    apprentice_video = models.ForeignKey('ApprenticeVideo', related_name='apprentice_scores',
                                         verbose_name=_('video do zadania'))
    task_mark_criteria = models.ForeignKey('TaskMarkCriteria', related_name='apprentice_scores',
                                           verbose_name=_('kryterium oceny'))

    class Meta:
        verbose_name = _('ocena filmu przez innego ucznia w kryterium')
        verbose_name_plural = _('oceny filmu przez innego ucznia w kryterium')

    def __str__(self):
        return '{} - {} / {}'.format(self.apprentice, self.apprentice_video, self.task_mark_criteria)


class ApprenticeTaskAnswerQuerySet(QuerySet):
    def completed(self):
        return self.filter(video_1__isnull=False, video_2__isnull=False)

    def by_apprentice(self, apprentice):
        return self.filter(apprentice=apprentice)


class ApprenticeTaskAnswerManager(Manager.from_queryset(ApprenticeTaskAnswerQuerySet)):
    def own(self, apprentice):
        return self.completed().filter(apprentice=apprentice)

    def others(self, apprentice):
        return self.completed().exclude(apprentice=apprentice)

    def random_from_task_answer(self, task_answer, max_quant):
        return self.completed().filter(task=task_answer.task).exclude(id=task_answer.id).order_by('?')[:max_quant]

    def from_path(self, path):
        return self.filter(task__path=path)


class ApprenticeTaskAnswer(TimeStampedModel):
    task = models.ForeignKey('Task', related_name='answers', verbose_name=_('zadanie'))
    apprentice = models.ForeignKey('users.Apprentice', related_name='answers', verbose_name=_('uczeń'))
    custom_title = models.CharField(max_length=50, blank=True, verbose_name=_('dodatkowy tytuł'))
    video_1 = models.OneToOneField('ApprenticeVideo', related_name='task_answer_as_1', blank=True, null=True,
                                   verbose_name=_('pierwsze video'))
    video_2 = models.OneToOneField('ApprenticeVideo', related_name='task_answer_as_2', blank=True, null=True,
                                   verbose_name=_('drugie video'))
    notification = models.BooleanField(default=False, verbose_name=_('powiadomienie o ocenie'))

    objects = ApprenticeTaskAnswerManager()

    def get_task_process(self):
        return int((float(self.get_status())) / (float(len(ApprenticeTaskAnswerStatus.names)) - 1) * 100)

    def get_status(self):
        if not self.video_1:
            return ApprenticeTaskAnswerStatus.FIRST_UPLOAD
        if not hasattr(self.video_1, 'mentor_text_score') and self.video_1.mentor_scores.count() == 0:
            return ApprenticeTaskAnswerStatus.BEFORE_MENTOR_RATE
        if self.task.exercises.active().count() != self.task.exercises.annotated_with_done_by_apprentice_count(
                self.apprentice):
            return ApprenticeTaskAnswerStatus.BEFORE_EXERCISES_DONE
        if not self.video_2:
            return ApprenticeTaskAnswerStatus.SECOND_UPLOAD
        return ApprenticeTaskAnswerStatus.COMPLETED

    class Meta:
        verbose_name = _('odpowiedź ucznia do zadania')
        verbose_name_plural = _('odpowiedzi uczniów do zadań')

    def slug(self):
        return self.task.slug()

    def save(self, **kwargs):
        if self.pk:
            old = ApprenticeTaskAnswer.objects.get(id=self.pk)
            if old.notification is False and self.notification is True:
                EmailSender.send_mentor_score_email(self.apprentice.email, {'BASE_SITE_URL': settings.BASE_SITE_URL})
        super(ApprenticeTaskAnswer, self).save(**kwargs)

    def __str__(self):
        return '{} - {}'.format(self.apprentice, self.task)


class ApprenticeVideo(TimeStampedModel):
    yt_video = EmbedVideoField(blank=True, verbose_name=_('link youtube'))
    reflection = models.TextField(blank=True, verbose_name=_('refleksja'))
    apprentices = models.ManyToManyField('users.Apprentice', blank=True, related_name='liked_apprentice_videos',
                                         verbose_name=_('ocenione przez'))

    class Meta:
        verbose_name = _('video do zadania')
        verbose_name_plural = _('video do zadania')

    def get_likes(self):
        return TaskMarkCriteria.objects.from_apprentice_video_with_likes(self)

    def __str__(self):
        try:
            task_answer_as_1 = self.task_answer_as_1
        except ApprenticeTaskAnswer.DoesNotExist:
            task_answer_as_1 = ''
        try:
            task_answer_as_2 = self.task_answer_as_2
        except ApprenticeTaskAnswer.DoesNotExist:
            task_answer_as_2 = ''

        return '{} - 1:<{}> / 2:<{}>'.format(self.yt_video, task_answer_as_1, task_answer_as_2)
