from datetime import date
from typing import TYPE_CHECKING, Any, Optional

from sqlalchemy import Index
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.associationproxy import AssociationProxy
from sqlalchemy.orm.collections import attribute_keyed_dict

from advanced_alchemy.base import BigIntAuditBase, BigIntBase

from .author import Author, BookAuthorLink
from .publisher import Publisher
from .tag import Tag, BookTagLink
from .library import Library


if TYPE_CHECKING:
    from chitai.database.models import (
        BookProgress,
        BookList,
        BookSeries,
        BookListLink,
    )


class Book(BigIntAuditBase):
    __tablename__ = "books"

    __table_args__ = (
        # Create trigram indexes
        Index(
            "ix_books_title_trigram",
            "title",
            postgresql_using="gin",
            postgresql_ops={"title": "gin_trgm_ops"},
        ),
    )

    library_id: Mapped[int] = mapped_column(ForeignKey("libraries.id"), nullable=False)
    library: Mapped["Library"] = relationship(back_populates="books")

    title: Mapped[str]
    subtitle: Mapped[Optional[str]]
    description: Mapped[Optional[str]]
    published_date: Mapped[Optional[date]]
    language: Mapped[Optional[str]]
    pages: Mapped[Optional[int]]
    cover_image: Mapped[Optional[str]]
    edition: Mapped[Optional[int]]
    path: Mapped[Optional[str]]

    library: Mapped[Library] = relationship(back_populates="books")

    author_links: Mapped[list["BookAuthorLink"]] = relationship(
        back_populates="book",
        cascade="all, delete-orphan",
        passive_deletes=True,
        order_by="BookAuthorLink.position",
        collection_class=ordering_list("position"),
    )

    authors: AssociationProxy[list["Author"]] = association_proxy(
        "author_links", "author", creator=lambda author: BookAuthorLink(author=author)
    )

    publisher_id: Mapped[Optional[int]] = mapped_column(ForeignKey("publishers.id"))
    publisher: Mapped[Optional[Publisher]] = relationship()

    tag_links: Mapped[list["BookTagLink"]] = relationship(
        back_populates="book",
        cascade="all, delete-orphan",
        passive_deletes=True,
        order_by="BookTagLink.position",
        collection_class=ordering_list("position"),
    )

    tags: AssociationProxy[list["Tag"]] = association_proxy(
        "tag_links", "tag", creator=lambda tag: BookTagLink(tag=tag)
    )

    identifiers: Mapped[list["Identifier"]] = relationship(
        cascade="all, delete-orphan",
        passive_deletes=True,
    )

    files: Mapped[list["FileMetadata"]] = relationship(
        cascade="all, delete-orphan", passive_deletes=True
    )

    list_links: Mapped[list["BookListLink"]] = relationship(
        back_populates="book", cascade="all, delete-orphan", passive_deletes=True
    )

    lists: AssociationProxy[list["BookList"]] = association_proxy(
        "list_links", "book_list"
    )

    series_id: Mapped[Optional[int]] = mapped_column(ForeignKey("book_series.id"))
    series: Mapped[Optional["BookSeries"]] = relationship()
    series_position: Mapped[Optional[str]]

    progress_records: Mapped[list["BookProgress"]] = relationship(
        cascade="all, delete-orphan", passive_deletes=True
    )

    @property
    def progress(self) -> Optional["BookProgress"]:
        return self.progress_records[0] if self.progress_records else None

    def __repr__(self) -> str:
        return f"Book({self.title=!r})"

    def to_dict(self, exclude: set[str] | None = None) -> dict[str, Any]:
        data = super().to_dict(exclude)
        data["authors"] = [author.name for author in self.authors]
        data["tags"] = [tag.name for tag in self.tags]
        data["publisher"] = self.publisher.name if self.publisher else None
        data["series"] = self.series.title if self.series else None
        data["files"] = [file.to_dict() for file in self.files]
        return data


class Identifier(BigIntBase):
    __tablename__ = "identifiers"

    name: Mapped[str] = mapped_column(primary_key=True)
    book_id: Mapped[int] = mapped_column(
        ForeignKey("books.id", ondelete="cascade"), primary_key=True
    )
    value: Mapped[str]

    def __repr__(self):
        return f"Identifier({self.name!r} : {self.value!r})"


class FileMetadata(BigIntBase):
    __tablename__ = "file_metadata"

    book_id: Mapped[int] = mapped_column(ForeignKey("books.id", ondelete="cascade"))
    book: Mapped[Book] = relationship(back_populates="files")
    hash: Mapped[str]
    path: Mapped[str]
    size: Mapped[int]
    content_type: Mapped[Optional[str]]

    def __repr__(self) -> str:
        return f"FileMetadata({self.path!r})"
