"""Tests for LibraryService"""

import pytest

from sqlalchemy.ext.asyncio import AsyncSession

from chitai.services import ShelfService
from chitai.database import models as m


import pytest
from sqlalchemy import select

from chitai.services.bookshelf import ShelfService
from chitai.services import BookService
from chitai.database.models.book_list import BookList, BookListLink
from chitai.database import models as m


@pytest.fixture
async def db_session(bookshelf_service: ShelfService) -> AsyncSession:
    return bookshelf_service.repository.session


@pytest.fixture(autouse=True)
async def test_books(db_session: AsyncSession) -> list[m.Book]:
    """Create test books in the database."""

    library = m.Library(
        name="Default Library",
        slug="default-library",
        root_path="./path",
        path_template="{author}/{title}",
        read_only=False,
    )

    db_session.add(library)

    books = [m.Book(title=f"Book {i}", library_id=1) for i in range(1, 8)]
    db_session.add_all(books)

    user = m.User(email="test_user@example.com", password="password123")
    db_session.add(user)

    await db_session.flush()
    await db_session.commit()
    return books


class TestAddBooks:
    async def test_add_books_to_empty_shelf(
        self, bookshelf_service: ShelfService, db_session
    ) -> None:
        """Successfully add books to an empty shelf."""
        # Create a shelf
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Add books
        await bookshelf_service.add_books(shelf.id, [1, 2, 3])

        # Verify books were added
        links = await db_session.execute(
            select(BookListLink).where(BookListLink.list_id == shelf.id)
        )
        added_links = links.scalars().all()
        assert len(added_links) == 3
        assert {link.book_id for link in added_links} == {1, 2, 3}

    async def test_add_books_preserves_positions(
        self, bookshelf_service: ShelfService, db_session
    ) -> None:
        """Verify books are assigned correct positions on the shelf."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        await bookshelf_service.add_books(shelf.id, [1, 2, 3])

        links = await db_session.execute(
            select(BookListLink)
            .where(BookListLink.list_id == shelf.id)
            .order_by(BookListLink.position)
        )
        added_links = links.scalars().all()

        # Verify positions are sequential starting from 0
        assert [link.position for link in added_links] == [0, 1, 2]

    async def test_add_books_to_shelf_with_existing_books(
        self, bookshelf_service: ShelfService, db_session
    ) -> None:
        """Adding books to a shelf with existing books assigns correct positions."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Add initial books
        await bookshelf_service.add_books(shelf.id, [1, 2])

        # Add more books
        await bookshelf_service.add_books(shelf.id, [3, 4])

        links = await db_session.execute(
            select(BookListLink)
            .where(BookListLink.list_id == shelf.id)
            .order_by(BookListLink.position)
        )
        added_links = links.scalars().all()

        # Verify new books continue from position 2
        assert len(added_links) == 4
        assert [link.position for link in added_links] == [0, 1, 2, 3]

    async def test_add_duplicate_books_is_idempotent(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Adding books already on shelf should not create duplicates."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Add books
        await bookshelf_service.add_books(shelf.id, [1, 2, 3])

        # Try to add overlapping books
        await bookshelf_service.add_books(shelf.id, [2, 3, 4])

        links = await db_session.execute(
            select(BookListLink).where(BookListLink.list_id == shelf.id)
        )
        added_links = links.scalars().all()

        # Should have 4 books total (1, 2, 3, 4), not 7
        assert len(added_links) == 4
        assert {link.book_id for link in added_links} == {1, 2, 3, 4}

    async def test_add_books_raises_on_nonexistent_shelf(
        self, bookshelf_service: ShelfService
    ) -> None:
        """Adding books to nonexistent shelf raises error."""
        with pytest.raises(Exception):  # Could be SQLAlchemyError or specific error
            await bookshelf_service.add_books(99999, [1, 2, 3])

    async def test_add_nonexistent_books_raises_error(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Adding nonexistent books raises ValueError."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        with pytest.raises(ValueError, match="One or more books not found"):
            await bookshelf_service.add_books(shelf.id, [99999, 99998])

    async def test_add_partial_nonexistent_books_raises_error(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Adding a mix of existent and nonexistent books raises error."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Book 1 might exist, but 99999 doesn't
        with pytest.raises(ValueError, match="One or more books not found"):
            await bookshelf_service.add_books(shelf.id, [1, 99999])

    async def test_add_empty_book_list(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Adding empty book list should return without error."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Should not raise
        await bookshelf_service.add_books(shelf.id, [])


class TestRemoveBooks:
    async def test_remove_books_from_shelf(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Successfully remove books from a shelf."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Add books
        await bookshelf_service.add_books(shelf.id, [1, 2, 3, 4])

        # Remove some books
        await bookshelf_service.remove_books(shelf.id, [2, 3])

        links = await db_session.execute(
            select(BookListLink).where(BookListLink.list_id == shelf.id)
        )
        remaining_links = links.scalars().all()

        assert len(remaining_links) == 2
        assert {link.book_id for link in remaining_links} == {1, 4}

    async def test_remove_all_books_from_shelf(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Removing all books should leave shelf empty."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        await bookshelf_service.add_books(shelf.id, [1, 2, 3])
        await bookshelf_service.remove_books(shelf.id, [1, 2, 3])

        links = await db_session.execute(
            select(BookListLink).where(BookListLink.list_id == shelf.id)
        )
        remaining_links = links.scalars().all()

        assert len(remaining_links) == 0

    async def test_remove_nonexistent_book_is_idempotent(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Removing books not on shelf should not raise error."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        await bookshelf_service.add_books(shelf.id, [1, 2, 3])

        # Remove books that don't exist on shelf - should not raise
        await bookshelf_service.remove_books(shelf.id, [99, 100])

        links = await db_session.execute(
            select(BookListLink).where(BookListLink.list_id == shelf.id)
        )
        remaining_links = links.scalars().all()

        # Original books should still be there
        assert len(remaining_links) == 3

    async def test_remove_books_raises_on_nonexistent_shelf(
        self, bookshelf_service: ShelfService
    ) -> None:
        """Removing books from nonexistent shelf raises error."""
        with pytest.raises(Exception):
            await bookshelf_service.remove_books(99999, [1, 2, 3])

    async def test_remove_empty_book_list(
        self, bookshelf_service: ShelfService, db_session: AsyncSession
    ) -> None:
        """Removing empty book list should return without error."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        await bookshelf_service.add_books(shelf.id, [1, 2, 3])

        # Should not raise
        await bookshelf_service.remove_books(shelf.id, [])

        links = await db_session.execute(
            select(BookListLink).where(BookListLink.list_id == shelf.id)
        )
        remaining_links = links.scalars().all()

        # All books should still be there
        assert len(remaining_links) == 3

    async def test_add_remove_add_sequence(
        self, bookshelf_service: ShelfService, db_session
    ) -> None:
        """Add books, remove from middle, then add more - positions should be maintained."""
        shelf = BookList(title="Test Shelf", user_id=1)
        db_session.add(shelf)
        await db_session.flush()

        # Add initial books [1, 2, 3, 4, 5]
        await bookshelf_service.add_books(shelf.id, [1, 2, 3, 4, 5])

        # Remove books from middle [2, 3, 4]
        await bookshelf_service.remove_books(shelf.id, [2, 3, 4])

        # Add more books [6, 7]
        await bookshelf_service.add_books(shelf.id, [6, 7])

        links = await db_session.execute(
            select(BookListLink)
            .where(BookListLink.list_id == shelf.id)
            .order_by(BookListLink.position)
        )
        final_links = links.scalars().all()

        # Should have [1, 5, 6, 7] with positions [0, 1, 2, 3]
        assert len(final_links) == 4
        assert [link.book_id for link in final_links] == [1, 5, 6, 7]
        assert [link.position for link in final_links] == [0, 1, 2, 3]
