import uuid

from django.db import models
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from taggit.managers import TaggableManager

from modelsearch import index
from modelsearch.queryset import SearchableQuerySetMixin


class Author(index.Indexed, models.Model):
    name = models.CharField(max_length=255)
    date_of_birth = models.DateField(null=True)

    search_fields = [
        index.SearchField("name"),
        index.AutocompleteField("name"),
        index.FilterField("date_of_birth"),
    ]

    def __str__(self):
        return self.name


class BookQuerySet(SearchableQuerySetMixin, models.QuerySet):
    pass


class Book(index.Indexed, models.Model):
    title = models.CharField(max_length=255)
    summary = models.TextField(blank=True)
    authors = models.ManyToManyField(Author, related_name="books")
    publication_date = models.DateField()
    number_of_pages = models.IntegerField()
    tags = TaggableManager()

    search_fields = [
        index.SearchField("title", boost=10.0),
        index.SearchField("summary", boost=2.0),
        index.AutocompleteField("title"),
        index.FilterField("title"),
        index.FilterField("authors"),
        index.RelatedFields("authors", Author.search_fields),
        index.FilterField("publication_date"),
        index.FilterField("number_of_pages"),
        index.RelatedFields(
            "tags",
            [
                index.SearchField("name"),
                index.FilterField("slug"),
            ],
        ),
        index.FilterField("tags"),
    ]

    objects = BookQuerySet.as_manager()

    def __str__(self):
        return self.title

    @classmethod
    def get_indexed_objects(cls):
        indexed_objects = super().get_indexed_objects()

        # Don't index books using Book class that they have a more specific type
        if cls is Book:
            indexed_objects = indexed_objects.exclude(
                id__in=Novel.objects.values_list("book_ptr_id", flat=True)
            )

            indexed_objects = indexed_objects.exclude(
                id__in=ProgrammingGuide.objects.values_list("book_ptr_id", flat=True)
            )

        # Exclude Books that have the title "Don't index me!"
        indexed_objects = indexed_objects.exclude(title="Don't index me!")

        return indexed_objects

    def get_indexed_instance(self):
        # Check if this object is a Novel or ProgrammingGuide and return the specific object
        novel = Novel.objects.filter(book_ptr_id=self.id).first()
        programming_guide = ProgrammingGuide.objects.filter(book_ptr_id=self.id).first()

        # Return the novel/programming guide object if there is one, otherwise return self
        return novel or programming_guide or self


class Character(models.Model):
    name = models.CharField(max_length=255)
    novel = models.ForeignKey(
        "Novel", related_name="characters", on_delete=models.CASCADE
    )

    def __str__(self):
        return self.name


class Novel(Book):
    setting = models.CharField(max_length=255)
    protagonist = models.OneToOneField(
        Character, related_name="+", null=True, on_delete=models.SET_NULL
    )

    search_fields = Book.search_fields + [
        index.SearchField("setting"),
        index.AutocompleteField("setting"),
        index.RelatedFields(
            "characters",
            [
                index.SearchField("name", boost=0.25),
            ],
        ),
        index.RelatedFields(
            "protagonist",
            [
                index.SearchField("name", boost=0.5),
                index.FilterField("novel"),
            ],
        ),
        index.FilterField("protagonist"),
    ]


class ProgrammingGuide(Book):
    programming_language = models.CharField(
        max_length=255,
        choices=[
            ("py", "Python"),
            ("js", "JavaScript"),
            ("rs", "Rust"),
        ],
    )

    search_fields = Book.search_fields + [
        index.SearchField("get_programming_language_display"),
        index.FilterField("programming_language"),
    ]


class UnindexedBook(index.Indexed, models.Model):
    title = models.CharField(max_length=255)
    publication_date = models.DateField()
    number_of_pages = models.IntegerField()
    tags = TaggableManager()

    search_fields = []

    def __str__(self):
        return self.title


class Advert(models.Model):
    text = models.CharField(max_length=255)

    def __str__(self):
        return self.text


class BlogCategory(models.Model):
    name = models.CharField(unique=True, max_length=80)

    def __str__(self):
        return self.name


class BlogCategoryBlogPage(models.Model):
    category = models.ForeignKey(
        BlogCategory, related_name="+", on_delete=models.CASCADE
    )
    page = ParentalKey(
        "ManyToManyBlogPage", related_name="categories", on_delete=models.CASCADE
    )

    def __str__(self):
        return f"BlogCategoryBlogPage: {self.category} - {self.page}"


class ManyToManyBlogPage(ClusterableModel):
    title = models.CharField(max_length=255)
    adverts = models.ManyToManyField(Advert, blank=True)


class AdvertWithCustomUUIDPrimaryKey(index.Indexed, models.Model):
    advert_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    text = models.CharField(max_length=255)

    search_fields = [
        index.SearchField("text"),
    ]

    def __str__(self):
        return self.text
