# src/chitai/services/bookshelf.py

# Third-party libraries
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
from advanced_alchemy.repository import SQLAlchemyAsyncRepository

# Local imports
from chitai.database.models.book_list import BookList, BookListLink
from chitai.services import BookService
from chitai.services.filters.book import BookshelfFilter
from chitai.database import models as m


class ShelfService(SQLAlchemyAsyncRepositoryService[BookList]):
    """Service for managing bookshelves and their contents."""

    class Repo(SQLAlchemyAsyncRepository[BookList]):
        """Repository for BookList entities."""

        model_type = BookList

    repository_type = Repo

    async def add_books(self, shelf_id: int, book_ids: list[int]) -> m.BookList:
        """
        Add books to the specified bookshelf.

        Creates BookListLink entries to associate books with the shelf, automatically
        assigning positions based on the current shelf size.

        Args:
            shelf_id: The ID of the shelf to add books to.
            book_ids: List of book IDs to add.

        Raises:
            ValueError: If the shelf does not exist.
        """

        shelf = await self.get(shelf_id, load=[m.BookList.book_links])

        async with BookService.new(session=self.repository.session) as book_service:
            # Verify all books exist
            total = await book_service.count(m.Book.id.in_(book_ids))
            if total != len(book_ids):
                raise ValueError("One or more books not found")

            # Get all books that exist on the shelf
            shelf_books = await book_service.list(BookshelfFilter(lists=[shelf_id]))

        # Filter out books that are already on the shelf to avoid adding them again
        existing_book_ids = [book.id for book in shelf_books]
        new_book_ids = [bid for bid in book_ids if bid not in existing_book_ids]

        #  Add new books
        if new_book_ids:
            for book_id in new_book_ids:
                shelf.book_links.append(BookListLink(book_id=book_id))

            await self.update(shelf)

        return await self.get(shelf_id, load=[m.BookList.book_links])

    async def remove_books(self, shelf_id: int, book_ids: list[int]) -> m.BookList:
        """
        Remove books from the specified bookshelf.

        Args:
            shelf_id: The ID of the shelf to remove books from.
            book_ids: List of book IDs to remove.

        Raises:
            ValueError: If the shelf does not exist.
        """
        shelf = await self.get(shelf_id, load=[BookList.book_links])

        book_ids_set = set(book_ids)  # for O(1) lookup
        for i in range(len(shelf.book_links) - 1, -1, -1):
            if shelf.book_links[i].book_id in book_ids_set:
                shelf.book_links.pop(i)

        await self.update(shelf)

        return await self.get(shelf_id, load=[m.BookList.book_links])
