from dataclasses import dataclass
from enum import StrEnum
from typing import Any, Literal, Optional, Sequence
from pydantic import BaseModel, Field
from datetime import datetime

class LinkTypes(StrEnum):
    NAVIGATION = "application/atom+xml;profile=opds-catalog;kind=navigation"
    ACQUISITION = "application/atom+xml;profile=opds-catalog;kind=acquisition"
    OPEN_SEARCH = "application/opensearchdescription+xml"

class AcquisitionRelations(StrEnum):
    _BASE = "http://opds-spec.org/acquisition"
    
    ACQUISITION = _BASE                         # A generic relation that indicates that the entry may be retrieved
    OPEN_ACCESS = f"{_BASE}/open-access"        # Entry may be retrieved without any requirement
    BORROW = f"{_BASE}/borrow"                  # Entry may be retrieved as part of a lending transaction
    BUY = f"{_BASE}/buy"                        # Entry may be retrieved as part of a purchase
    SAMPLE = f"{_BASE}/sample"                  # Subset of the entry may be retrieved
    PREVIEW = f"{_BASE}/preview"                # Subset of the entry may be retrieved
    SUBSCRIBE = f"{_BASE}/subscribe"            # Entry my be retrieved as a part of a subscription

class NavigationRelations(StrEnum):
    _BASE = ""

class LinkRelations(StrEnum):
    """ Link types for OPDSv1.2 related resources

    https://specs.opds.io/opds-1.2.html#6-additional-link-relations
    """

    _BASE = "http://opds-spec.org"

    START = "start"                             # The OPDS catalog root
    SUBSECTION = "subsection"                   # an OPDS feed not better described by any of the below relations
    SHELF = f"{_BASE}/shelf"                    # Entries acquired by the euser
    SUBSCRIPTIONS = f"{_BASE}/subscriptions"    # Entries available with users's subscription
    NEW = f"{_BASE}/sort/new"                   # Newest entries
    POPULAR = f"{_BASE}/sort/popular"           # Most popular entries
    FEATURED = f"{_BASE}/featured"              # Entries selected for promotion
    RECOMMENDED = f"{_BASE}/recommended"        # Entries recommended to the specific user 




class Feed(BaseModel):  # OPDS Catalog root element
    xmlns: Literal["http://www.w3.org/2005/Atom"] = Field(
        default="http://www.w3.org/2005/Atom", serialization_alias="@xmlns"
    )
    id: str
    link: list["Link"]
    title: str
    updated: datetime
    author: Optional["Author"] = None
    entry: list["Entry"] = Field(default_factory=list)

    def model_dump(self, **kwargs) -> dict:
        data = super().model_dump(**kwargs)
        return {"feed": data}


class NavigationFeed(Feed): ...


class AcquisitionFeed(Feed):
    xmlns_dc: str | None = Field(
        default="http://purl.org/dc/terms/", serialization_alias="@xmlns:dc"
    )
    xmlns_opds: str | None = Field(
        default="http://opds-spec.org/2010/catalog", serialization_alias="@xmlns:opds"
    )


class Link(BaseModel):
    rel: str = Field(serialization_alias="@rel")
    href: str = Field(serialization_alias="@href")
    type: str = Field(serialization_alias="@type")
    title: str | None = Field(default=None, serialization_alias="@title")


class AcquisitionLink(Link):
    rel: str = Field(
        default=AcquisitionRelations.OPEN_ACCESS, serialization_alias="@rel"
    )


class ImageLink(Link):
    rel: str = Field(default="http://opds-spec.org/image", serialization_alias="@rel")


class Author(BaseModel):
    name: str
    uri: str | None = None


class AtomCategory(BaseModel):
    # https://datatracker.ietf.org/doc/html/rfc4287#section-4.2.2

    # MUST have a term attribute
    term: str = Field()  # The category to which the entry or feed belongs to

    # MAY have scheme and label attributes
    scheme: str | None = Field(
        default=None, serialization_alias="@scheme"
    )  # IRI that identifies a categorization scheme
    label: str | None = Field(
        default=None, serialization_alias="@label"
    )  # Human readable label for display in end-user applications


class AcquisitionFeedLink(Link):
    rel: str = Field(default=LinkRelations.SUBSECTION, serialization_alias="@rel")
    type: str = Field(
        default=LinkTypes.ACQUISITION,
        serialization_alias="@type",
    )


class NavigationFeedLink(Link):
    type: str = Field(
        default=LinkTypes.NAVIGATION, serialization_alias="@type"
    )

class Content(BaseModel):
    type: Literal["text"] = Field(default="text", serialization_alias="@type")
    text: str = Field(serialization_alias="#text")


class Summary(BaseModel):
    type: Literal["text"] = Field(default="text", serialization_alias="@type")
    text: str = Field(serialization_alias="#text")


class Entry(BaseModel):
    #  https://specs.opds.io/opds-1.2.html#5-opds-catalog-entry-documents

    # TODO Follow for RFC 3339 date formats

    # MUST have these fields
    id: str
    title: str
    updated: datetime = Field(default_factory=datetime.now)

    # SHOULD have these fields
    dc_identifier: list[str] | None = Field(
        default=None, serialization_alias="dc:identifier"
    )
    dc_issued: str | None = Field(
        default=None, serialization_alias="dc:issued"
    )  # Publication date of the book
    author: list[Author] | None = None
    category: str | None = Field(default=None)
    rights: str | None = Field(default=None)
    link: Sequence["Link"] = Field(default_factory=list)
    summary: str | None = None
    content: Optional["Content"] = None

    # MAY have these fields
    contributor: str | None = (
        None  # represent contributors to the Publication beside its creators.
    )
    published: str | None = (
        None  # indicates when the OPDS Catalog Entry was first accessible
    )

    dc_language: str | None = Field(default=None, serialization_alias="dc:language")
    dc_publisher: str | None = Field(default=None, serialization_alias="dc:publisher")

    def model_dump(self, **kwargs) -> dict[str, Any]:
        data = super().model_dump(**kwargs)
        return {"entry": data}

@dataclass
class PaginationResult:
    next_link: Optional[Link]
    prev_link: Optional[Link]
    current_offset: int
    total_count: int