
from typing import Any, Callable, Sequence
from urllib.parse import quote_plus, urlencode
from litestar import Request
import xmltodict
from datetime import datetime
from chitai.database import models as m
from .models import (
    Author,
    Entry,
    Link,
    LinkTypes,
    ImageLink,
    AcquisitionLink,
    AcquisitionFeed,
    AcquisitionFeedLink,
    NavigationFeed,
    NavigationFeedLink,
    PaginationResult
)

def get_opensearch_document(base_url: str = "/opds/search?") -> str:
    search = {
        "OpenSearchDescription": {
            "@xmlns": "http://a9.com/-/spec/opensearch/1.1/",
            "Url": {
                "@type": "application/atom+xml;profile=opds-catalog;kind=acquisition",
                "@template": base_url + "searchString={searchTerms}",
            },
        }
    }

    return xmltodict.unparse(search)


def convert_book_to_entry(book: m.Book) -> Entry:
    return Entry(
        id=str(book.id),
        title=book.title,
        updated=book.updated_at,
        author=[Author(name=author.name) for author in book.authors],
        dc_identifier=[f"{id.name}:{id.value}" for id in book.identifiers],
        dc_publisher=book.publisher.name if book.publisher is not None else None,
        dc_language=book.language,
        dc_issued=str(book.published_date),
        summary=book.description,
        link=[
            ImageLink(href=f"/{book.cover_image}", type="image/webp"),
            *[
                AcquisitionLink(
                    href=f"/opds/download/{book.id}/{file.id}", type=file.content_type
                )
                for file in book.files
            ],
        ],
    )


def create_acquisition_feed(
    id: str,
    title: str,
    url: str,
    books: Sequence[m.Book],
    updated: datetime = datetime.now(),
    links: Sequence[Link] = list(),
) -> str:
    feed = AcquisitionFeed(
        id=id,
        title=title,
        updated=updated,
        link=[
            Link(
                rel="self",
                href=url,
                type="application/atom+xml;profile=opds-catalog;kind=acquisition",
            ),
            *links,
        ],
        entry=[convert_book_to_entry(book) for book in books],
    )

    return xmltodict.unparse(
        feed.model_dump(by_alias=True, exclude_none=True, exclude_defaults=False),
        pretty=True,
    )


def create_navigation_feed(
    id: str,
    title: str,
    self_url: str,
    entries: list[Entry] = list(),
    links: Sequence[Link] = list(),
    updated: datetime = datetime.now(),
    parent_url: str | None = None,
) -> str:

    self_link = Link(rel="self", href=self_url, type=LinkTypes.NAVIGATION)
    parent_link = Link(
        rel="up" if parent_url else "start",
        href=parent_url if parent_url else self_url,
        type=LinkTypes.NAVIGATION,
    )

    feed = NavigationFeed(
        id=id,
        link=[self_link, parent_link, *links],
        title=title,
        updated=updated,
        entry=entries,
    )

    return xmltodict.unparse(
        feed.model_dump(by_alias=True, exclude_none=True, exclude_defaults=False),
        pretty=True,
    )

def create_library_navigation_feed(library: m.Library) -> str:

    entries = [
        Entry(
            id=f"/opds/library/{library.id}/all-books",
            title='All Books',
            link=[
                AcquisitionFeedLink(
                    rel="subsection",
                    href=f"/opds/acquisition?libraries={library.id}&paginated=1&pageSize=50&feed_title=AllBooks&feed_id=/opds/library/{library.id}/all-books",
                    title="All Books",
                )
            ]
        ),
        Entry(
            id=f"/opds/library/{library.id}/recently-added",
            title="Recently Added",
            link=[
                NavigationFeedLink(
                    rel="http://opds-spec.org/sort/new",
                    href=f"/opds/acquisition?libraries={library.id}&orderBy=created_at&pageSize=50&feed_title=RecentlyAdded&feed_id=/opds/library/{library.id}/recently-added",
                    title="Recently Added"
                )
            ]
        ),
        Entry(
            id=f"/opds/library/{library.id}/shelves",
            title="Bookshelves",
            link=[
                NavigationFeedLink(
                    rel="subsection",
                    href=f"/opds/library/{library.id}/shelves?paginated=1&pageSize=10",
                    title="Bookshelves"
                )
            ]
        ),
        Entry(
            id=f"/opds/library/{library.id}/tags",
            title="Tags",
            link=[
                NavigationFeedLink(
                    rel="subsection",
                    href=f"/opds/library/{library.id}/tags?paginated=1&pageSize=10",
                    title="Tags"
                )
            ]
        ),
        Entry(
            id=f"/opds/library/{library.id}/authors",
            title="Authors",
            link=[
                NavigationFeedLink(
                    rel="subsection",
                    href=f"/opds/library/{library.id}/authors?paginated=1&pageSize=10",
                    title="Authors"
                )
            ]
        ),
        Entry(
            id=f"/opds/library/{library.id}/publishers",
            title="Publishers",
            link=[
                NavigationFeedLink(
                    rel="subsection",
                    href=f"/opds/library/{library.id}/publishers?paginated=1&pageSize=10",
                    title="Publishers"
                )
            ]
        ),

    ]

    feed = create_navigation_feed(
        id=f'/library/{library.id}',
        title=library.name,
        self_url=f'/opds/library/{library.id}',
        links=[
            
        ],
        entries=entries
    )

    return feed

def create_collection_navigation_feed(
    library: m.Library,
    collection_type: str,
    items: Sequence[m.BookList | m.Tag | m.Author | m.Publisher | m.BookSeries],
    links: list[Link] = list(),
    # Title is usually derived from the model's name or title
    get_title: Callable[[Any], str] = lambda x: getattr(x, 'title', getattr(x, 'name', str(x)))
) -> str:
    
    entries = [
        Entry(
            id=f"/opds/library/{library.id}/{collection_type}/{item.id}",
            title=get_title(item),
            link=[
                AcquisitionFeedLink(
                    href=f"/opds/acquisition?{collection_type}={item.id}&pageSize=50&paginated=1&feed_title={quote_plus(get_title(item))}&feed_id=/opds/library/{library.id}/{collection_type}/{item.id}&search=True",
                    title=get_title(item),
                )
            ]
        ) for item in items
    ]

    return create_navigation_feed(
        id=f"/opds/library/{library.id}/{collection_type}",
        title=collection_type.title(),
        self_url=f'/opds/library/{library.id}/{collection_type}',
        entries=entries,
        links=links
    )

def create_next_paginated_link(
        request: Request,
        total: int,
        current_count: int,
        offset: int,
        feed_title: str
    ) -> Link | None:
        if total <= current_count + offset:
            return None
            
        params = dict(request.query_params)
        params['currentPage'] = params.get('currentPage', 1) + 1
        
        next_url = f"{request.url.path}?{urlencode(list(params.items()), doseq=True)}"
        
        return Link(
            rel="next",
            href=next_url,
            title=feed_title,
            type=LinkTypes.NAVIGATION
        )

def create_search_link(
    request: Request,
    exclude_params: set[str] | None = None
) -> Link:
    """Create search link with current filters applied"""
    if exclude_params is None:
        exclude_params = {'currentPage', 'feed_title', 'feed_id', 'search', 'paginated'}
    
    params = {
        k: v for k, v in request.query_params.items() 
        if k not in exclude_params
    }
    
    return Link(
        rel="search",
        href=f"/opds/opensearch?{urlencode(list(params.items()), doseq=True)}",
        type=LinkTypes.OPEN_SEARCH,
        title="Search books",
    )



def create_pagination_links(
    request: Request,
    total: int,
    limit: int,
    offset: int,
    feed_title: str = "",
    link_type: str = LinkTypes.ACQUISITION,
) -> PaginationResult:
    """Create next/prev pagination links using limit/offset"""
    
    next_link = None
    prev_link = None
    
    # Create next link if there are more items
    if offset + limit < total:
        params = dict(request.query_params)
        # Calculate next page number
        current_page = (offset // limit) + 1
        params['currentPage'] = current_page + 1
        
        next_url = f"{request.url.path}?{urlencode(params, doseq=True)}"
        next_link = Link(
            rel="next",
            href=next_url,
            title=f"{feed_title} - Next",
            type=link_type
        )
    
    # Create previous link if not on first page
    if offset > 0:
        params = dict(request.query_params)
        # Calculate previous page number
        current_page = (offset // limit) + 1
        params['currentPage'] = max(1, current_page - 1)
        
        prev_url = f"{request.url.path}?{urlencode(params, doseq=True)}"
        prev_link = Link(
            rel="previous",
            href=prev_url,
            title=f"{feed_title} - Previous",
            type=link_type
        )
    
    return PaginationResult(next_link, prev_link, offset, total)
