from django.db import models
from django.db.models import Q
from django.urls import reverse

from app.base.forms.fields import DateField
from app.base.models.account import Account
from app.base.models.booking import Booking
from app.base.models.course import Course
from app.base.models.course_visit import CourseVisit

from datetime import datetime, date
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from app.base.models import Note, TraitMapping


class Person(models.Model):
    created: 'models.DateField[date]' = DateField('Angelegt', editable=False)
    last_visit: 'models.DateField[date]' = DateField(
        'Letzter Besuch', editable=False)

    uuid = models.CharField('Karten-ID', max_length=200, blank=True)
    first_name = models.CharField('Vorname', max_length=200)
    last_name = models.CharField('Nachname', max_length=200)
    birth_date: 'models.DateField[date]' = DateField('Geburtsdatum')
    street = models.CharField('Straße & Hausnummer', max_length=200)
    zip_code = models.CharField('PLZ', max_length=10)
    city = models.CharField('Stadt', max_length=200)
    email = models.EmailField('Email', blank=True, null=True)
    phone = models.CharField('Telefon', max_length=200, blank=True, null=True)

    identified = models.BooleanField(
        'Ausweis vorgezeigt', default=False)
    agreed_to_terms_of_service = models.BooleanField(
        'Nutzungsbedingungen zugestimmt', default=False)

    # related_name
    account: 'models.OneToOneField[Account]'
    note: 'models.OneToOneField[Note]'
    courses: 'models.QuerySet[CourseVisit]'
    instructed: 'models.QuerySet[CourseVisit]'
    traits: 'models.QuerySet[TraitMapping]'

    class Meta:
        verbose_name = 'Werkstattnutzer:in'
        verbose_name_plural = 'Werkstattnutzer:innen'
        ordering = ('-last_visit',)
        # indexes = [
        #     models.Index(fields=['first_name', 'last_name']),
        #     models.Index(Latest,fields=["first_name"],name="first_name_idx"),
        # ]

    def get_absolute_url(self):
        return reverse('person:detail', kwargs={'pk': self.pk})

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

    def save(self, *args, **kwargs):
        is_new = self._state.adding
        if is_new:
            self.created = date.today()
            self.last_visit = date.today()
        rv = super().save(*args, **kwargs)
        if is_new:
            Account.objects.create(user=self)
        return rv

    @property
    def display_name(self):
        return f'{self.first_name} {self.last_name}'

    @property
    def address(self):
        return f'{self.street}, {self.zip_code} {self.city}'

    @property
    def missing_courses(self):
        mandatory_courses = Course.objects.filter(mandatory=True)
        completed = set(self.courses.values_list('course', flat=True))
        return [x for x in mandatory_courses if x.pk not in completed]

    @property
    def attributes(self):
        return self.traits_at_date(datetime.now()).values_list(
            'pk', 'trait__key', 'trait__label')

    @property
    def upcoming_attributes(self):
        today = datetime.now()
        return self.traits.filter(Q(valid_from__gt=today)).values_list(
            'pk', 'trait__key', 'trait__label', 'valid_from')

    @property
    def current_checkin(self):
        return Booking.currently_open_checkin(self)

    def traits_at_date(self, date: datetime):
        return self.traits.filter(
            Q(valid_from__lte=date),
            Q(valid_until__gte=date) | Q(valid_until=None))

    def lookup_last_visit(self) -> date:
        ''' Return newest date from either booking, course, or created. '''
        last_booking = Booking.objects.filter(
            user=self).order_by('-begin_time').first()
        last_course = CourseVisit.objects.filter(
            Q(participant=self) | Q(teacher=self)).order_by('-date').first()
        available_dates = [self.created]
        if last_booking:
            available_dates.append(last_booking.begin_time.date())
        if last_course:
            available_dates.append(last_course.date)
        return max(available_dates)

    def update_last_visit(self, new_visit: 'date|None') -> None:
        if self.last_visit == new_visit:
            return  # no need to update
        if not new_visit or new_visit < self.last_visit:
            new_visit = self.lookup_last_visit()
        self.last_visit = new_visit
        self.save()
