import uuid

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.query import ModelIterable
from django.utils.functional import cached_property


class Author(models.Model):
    name = models.CharField(max_length=50, unique=True)
    first_book = models.ForeignKey(
        "Book", models.CASCADE, related_name="first_time_authors"
    )
    favorite_authors = models.ManyToManyField(
        "self", through="FavoriteAuthors", symmetrical=False, related_name="favors_me"
    )

    class Meta:
        ordering = ["id"]

    def __str__(self):
        return self.name


class AuthorWithAge(Author):
    author = models.OneToOneField(Author, models.CASCADE, parent_link=True)
    age = models.IntegerField()


class FavoriteAuthors(models.Model):
    author = models.ForeignKey(
        Author, models.CASCADE, to_field="name", related_name="i_like"
    )
    likes_author = models.ForeignKey(
        Author, models.CASCADE, to_field="name", related_name="likes_me"
    )

    class Meta:
        ordering = ["id"]


class AuthorAddress(models.Model):
    author = models.ForeignKey(
        Author, models.CASCADE, to_field="name", related_name="addresses"
    )
    address = models.TextField()

    class Meta:
        ordering = ["id"]


class Book(models.Model):
    title = models.CharField(max_length=255)
    authors = models.ManyToManyField(Author, related_name="books")

    class Meta:
        ordering = ["id"]


class BookWithYear(Book):
    book = models.OneToOneField(Book, models.CASCADE, parent_link=True)
    published_year = models.IntegerField()
    aged_authors = models.ManyToManyField(AuthorWithAge, related_name="books_with_year")


class Bio(models.Model):
    author = models.OneToOneField(
        Author,
        models.CASCADE,
        primary_key=True,
        to_field="name",
    )
    books = models.ManyToManyField(Book, blank=True)


class Reader(models.Model):
    name = models.CharField(max_length=50)
    books_read = models.ManyToManyField(Book, related_name="read_by")

    class Meta:
        ordering = ["id"]

    def __str__(self):
        return self.name


class BookReview(models.Model):
    # Intentionally does not have a related name.
    book = models.ForeignKey(BookWithYear, models.CASCADE, null=True)
    notes = models.TextField(null=True, blank=True)


# Models for default manager tests


class Qualification(models.Model):
    name = models.CharField(max_length=10)

    class Meta:
        ordering = ["id"]


class ModelIterableSubclass(ModelIterable):
    pass


class TeacherQuerySet(models.QuerySet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._iterable_class = ModelIterableSubclass


class TeacherManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().prefetch_related("qualifications")


class Teacher(models.Model):
    name = models.CharField(max_length=50)
    qualifications = models.ManyToManyField(Qualification)

    objects = TeacherManager()
    objects_custom = TeacherQuerySet.as_manager()

    class Meta:
        ordering = ["id"]

    def __str__(self):
        return "%s (%s)" % (
            self.name,
            ", ".join(q.name for q in self.qualifications.all()),
        )


class Department(models.Model):
    name = models.CharField(max_length=50)
    teachers = models.ManyToManyField(Teacher)

    class Meta:
        ordering = ["id"]


# GenericRelation/GenericForeignKey tests


class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(
        ContentType,
        models.CASCADE,
        related_name="taggeditem_set2",
    )
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")
    created_by_ct = models.ForeignKey(
        ContentType,
        models.SET_NULL,
        null=True,
        related_name="taggeditem_set3",
    )
    created_by_fkey = models.PositiveIntegerField(null=True)
    created_by = GenericForeignKey(
        "created_by_ct",
        "created_by_fkey",
    )
    favorite_ct = models.ForeignKey(
        ContentType,
        models.SET_NULL,
        null=True,
        related_name="taggeditem_set4",
    )
    favorite_fkey = models.CharField(max_length=64, null=True)
    favorite = GenericForeignKey("favorite_ct", "favorite_fkey")

    class Meta:
        ordering = ["id"]


class Article(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=20)


class Bookmark(models.Model):
    url = models.URLField()
    tags = GenericRelation(TaggedItem, related_query_name="bookmarks")
    favorite_tags = GenericRelation(
        TaggedItem,
        content_type_field="favorite_ct",
        object_id_field="favorite_fkey",
        related_query_name="favorite_bookmarks",
    )

    class Meta:
        ordering = ["id"]


class Comment(models.Model):
    comment = models.TextField()

    # Content-object field
    content_type = models.ForeignKey(ContentType, models.CASCADE, null=True)
    object_pk = models.TextField()
    content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
    content_type_uuid = models.ForeignKey(
        ContentType, models.CASCADE, related_name="comments", null=True
    )
    object_pk_uuid = models.TextField()
    content_object_uuid = GenericForeignKey(
        ct_field="content_type_uuid", fk_field="object_pk_uuid"
    )

    class Meta:
        ordering = ["id"]


# Models for lookup ordering tests


class House(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=255)
    owner = models.ForeignKey("Person", models.SET_NULL, null=True)
    main_room = models.OneToOneField(
        "Room", models.SET_NULL, related_name="main_room_of", null=True
    )

    class Meta:
        ordering = ["id"]


class Room(models.Model):
    name = models.CharField(max_length=50)
    house = models.ForeignKey(House, models.CASCADE, related_name="rooms")

    class Meta:
        ordering = ["id"]


class Person(models.Model):
    name = models.CharField(max_length=50)
    houses = models.ManyToManyField(House, related_name="occupants")

    @property
    def primary_house(self):
        # Assume business logic forces every person to have at least one house.
        return sorted(self.houses.all(), key=lambda house: -house.rooms.count())[0]

    @property
    def all_houses(self):
        return list(self.houses.all())

    @cached_property
    def cached_all_houses(self):
        return self.all_houses

    class Meta:
        ordering = ["id"]


# Models for nullable FK tests


class Employee(models.Model):
    name = models.CharField(max_length=50)
    boss = models.ForeignKey("self", models.SET_NULL, null=True, related_name="serfs")

    class Meta:
        ordering = ["id"]


# Ticket #19607


class LessonEntry(models.Model):
    name1 = models.CharField(max_length=200)
    name2 = models.CharField(max_length=200)


class WordEntry(models.Model):
    lesson_entry = models.ForeignKey(LessonEntry, models.CASCADE)
    name = models.CharField(max_length=200)


# Ticket #21410: Regression when related_name="+"


class Author2(models.Model):
    name = models.CharField(max_length=50, unique=True)
    first_book = models.ForeignKey(
        "Book", models.CASCADE, related_name="first_time_authors+"
    )
    favorite_books = models.ManyToManyField("Book", related_name="+")

    class Meta:
        ordering = ["id"]


# Models for many-to-many with UUID pk test:


class Pet(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=20)
    people = models.ManyToManyField(Person, related_name="pets")


class Flea(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    current_room = models.ForeignKey(
        Room, models.SET_NULL, related_name="fleas", null=True
    )
    pets_visited = models.ManyToManyField(Pet, related_name="fleas_hosted")
    people_visited = models.ManyToManyField(Person, related_name="fleas_hosted")