from datetime import date, timedelta
from typing import Optional
from uuid import uuid4

from django.db import models, IntegrityError
from django.utils import timezone

import library


class PersonQuerySet(models.QuerySet):
    """
    Manager for people.

    """

    def employees(self) -> models.QuerySet:
        """Get only employees."""
        return self.filter(role=Person.Role.EMPLOYEE)

    def clients(self) -> models.QuerySet:
        """Get only clients."""
        return self.filter(role=Person.Role.CLIENT)


class Person(models.Model):
    """
    Base class for people, employees and clients.

    """

    class Role(models.IntegerChoices):
        EMPLOYEE = 0, "employee"
        CLIENT = 1, "client"

    role = models.PositiveSmallIntegerField(default=0, choices=Role.choices, db_index=True, verbose_name="role")
    user = models.OneToOneField("auth.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="person")
    uuid = models.UUIDField(default=uuid4, db_index=True, verbose_name="UUID")
    first_name = models.CharField(max_length=64, blank=False, verbose_name="first name")
    last_name = models.CharField(max_length=64, blank=False, verbose_name="last name")
    birth_date = models.DateField(default=date(2000, 1, 1), verbose_name="birth date")
    creation_date = models.DateTimeField(auto_now_add=True, verbose_name="creation date")
    picture = models.ImageField(max_length=256, null=True, blank=True, upload_to="pictures", verbose_name="picture")
    objects = PersonQuerySet.as_manager()

    class Meta:
        verbose_name = "person"
        verbose_name_plural = "people"

    def __str__(self):
        return f"{self.get_full_name()} ({self.get_role_display()})"

    def get_full_name(self):
        return f"{self.first_name} {self.last_name}"

    def borrow(self, book: "library.models.Book", duration: int = 7) -> Optional["library.models.Loan"]:
        """
        Borrow a book.

        Args:
            book: book instance to borrow
            duration: expected duration of loan in days

        Returns:
            If the book can be borrowed, return the new `Loan` object.
            If not, return `None`.

        """
        try:
            deadline = timezone.now() + timedelta(days=duration)
            loan = self.loans.create(person=self, book=book, expected_return=deadline)
            return loan
        except IntegrityError:
            return None
