from django.db import models
from otree.models import Participant
from django.db import transaction
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from otree_tools import cp
from django.db.models import F, ExpressionWrapper, DurationField, Sum, Min, Case, When, Count, FloatField, \
    IntegerField
from django.db.models.functions import Cast
from datetime import timedelta
from otree_tools.constants import *

allowed_export_tracker_requests = r'(time|focus_per_page|focus_raw)'


class GeneralEvent(models.Model):
    class Meta:
        get_latest_by = 'timestamp'
        abstract = True

    page_name = models.CharField(max_length=1000)
    participant = models.ForeignKey(to=Participant, related_name="%(app_label)s_%(class)s_events")
    app_name = models.CharField(max_length=1000)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    player_id = models.PositiveIntegerField()
    player = GenericForeignKey('content_type', 'player_id')
    timestamp = models.DateTimeField()
    round_number = models.PositiveIntegerField()


class Enter(GeneralEvent):
    wait_for_images = models.BooleanField(choices=WAITFORIMAGES_CHOICES, default=True)

    def __str__(self):
        return f'id: {self.pk}, Enter: {self.page_name}; time: {self.timestamp}'


class ExitExportManager(models.Manager):
    """Manager for correctly exporting data to templates and csv from time trackers."""

    def time(self):
        tot_exits = super().get_queryset().filter(enter__isnull=False, ). \
            values('participant',
                   'app_name',
                   'page_name',
                   'player_id',
                   'participant__code',
                   'participant__session__code',
                   'round_number',
                   ). \
            annotate(diff=Sum(ExpressionWrapper(F('timestamp') - F('enter__timestamp'),
                                                output_field=DurationField())),
                     timestamp=F('timestamp'),
                     enter_timestamp=F('enter__timestamp'),
                     exit_type=F('exit_type'),
                     wait_for_images=F('enter__wait_for_images'))
        return tot_exits


class Exit(GeneralEvent):
    objects = models.Manager()
    export = ExitExportManager()
    enter = models.OneToOneField(to='Enter', related_name='exit', null=True)
    timestamp = models.DateTimeField()
    exit_type = models.IntegerField(choices=EXITTYPES)

    def __str__(self):
        return f'id: {self.pk}, Exit: {self.page_name}; time: {self.timestamp}; Type: {self.get_exit_type_display()} '


class FocusExportManager(models.Manager):
    def focus_per_page(self):
        return self.model.objects.get_per_page_report()

    def focus_raw(self):
        return super().get_queryset()


class FocusManager(models.Manager):
    def _get_tracked_time(self, player_id, participant_id, page_name, focus_type):
        tot_exits = super().get_queryset().values('participant',
                                                  'page_name',
                                                  'player_id'). \
            filter(player_id=player_id,
                   participant_id=participant_id,
                   page_name=page_name,
                   entry__isnull=False,
                   ). \
            aggregate(
            diff=Sum(Case(
                When(event_num_type__in=focus_type,
                     then=ExpressionWrapper(F('timestamp') - F('entry__timestamp'),
                                            output_field=DurationField())),
                output_field=DurationField(),
            )),
            num_unfocus=Count(Case(When(event_num_type=FocusExitEventTypes.FOCUS_OFF, then=1)))
        )

        return tot_exits

    def get_focused_time_per_page(self, player_id, participant_id, page_name):
        diff = self._get_tracked_time(player_id, participant_id, page_name, focus_exit_codes)['diff'] or timedelta()
        return diff.total_seconds()

    def get_unfocused_time_per_page(self, player_id, participant_id, page_name):
        diff = self._get_tracked_time(player_id, participant_id, page_name, focus_enter_codes)['diff'] or timedelta()
        return diff.total_seconds()

    def num_focusoff_events(self, player_id, participant_id, page_name):
        num_unfocus = self._get_tracked_time(player_id, participant_id, page_name, focus_exit_codes)['num_unfocus']
        return num_unfocus

    def get_per_page_report(self):
        q = super().get_queryset().values('participant',
                                          'app_name',
                                          'page_name',
                                          'player_id',
                                          'participant__code',
                                          'participant__session__code',
                                          'round_number'). \
            filter(entry__isnull=False). \
            annotate(
            focused_time=Sum(Case(
                When(event_num_type__in=focus_exit_codes,
                     then=ExpressionWrapper(F('timestamp') - F('entry__timestamp'),
                                            output_field=DurationField())),
                output_field=DurationField(),
            )),
            unfocused_time=Sum(Case(
                When(event_num_type__in=focus_enter_codes,
                     then=ExpressionWrapper(F('timestamp') - F('entry__timestamp'),
                                            output_field=DurationField())),
                output_field=DurationField(),
            )),
            num_unfocus=Count(Case(When(event_num_type=FocusExitEventTypes.FOCUS_OFF.value, then=1))),
        ).annotate(total_time=Sum(ExpressionWrapper(F('timestamp') - F('entry__timestamp'),
                                                    output_field=DurationField())))

        return q


class FocusEvent(GeneralEvent):
    objects = FocusManager()
    export = FocusExportManager()
    event_desc_type = models.CharField(max_length=1000)
    event_num_type = models.IntegerField(choices=FOCUS_EVENT_TYPES)
    # we track focus events that have corresponding closuring focus event to
    # correctly calculate focus on/focus off time
    entry = models.OneToOneField(to='FocusEvent',
                                 related_name='closure',
                                 null=True,
                                 )

    def __str__(self):
        return f'id: {self.pk}, event_desc_type: {self.event_desc_type}; time: {self.timestamp};'
