"""Модуль работы с книгами"""

from datetime import datetime, timezone
from typing import List

from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
from sqlmodel import Session, select, col, func

from library_service.auth import RequireStaff
from library_service.settings import get_session
from library_service.models.enums import BookStatus
from library_service.models.db import (
    Author,
    AuthorBookLink,
    Book,
    GenreBookLink,
    Genre,
    BookUserLink,
)
from library_service.models.dto import (
    AuthorRead,
    BookCreate,
    BookList,
    BookRead,
    BookUpdate,
    GenreRead,
)
from library_service.models.dto.misc import (
    BookWithAuthorsAndGenres,
    BookFilteredList,
)


router = APIRouter(prefix="/books", tags=["books"])


def close_active_loan(session: Session, book_id: int) -> None:
    """Закрывает активную выдачу книги при изменении статуса"""
    active_loan = session.exec(
        select(BookUserLink)
        .where(BookUserLink.book_id == book_id)
        .where(BookUserLink.returned_at == None)  # noqa: E711
    ).first()

    if active_loan:
        active_loan.returned_at = datetime.now(timezone.utc)
        session.add(active_loan)


@router.get(
    "/filter",
    response_model=BookFilteredList,
    summary="Фильтрация книг",
    description="Фильтрация списка книг по названию, авторам и жанрам с пагинацией",
)
def filter_books(
    session: Session = Depends(get_session),
    q: str | None = Query(None, max_length=50, description="Поиск"),
    min_page_count: int | None = Query(
        None, ge=0, description="Минимальное количество страниц"
    ),
    max_page_count: int | None = Query(
        None, ge=0, description="Максимальное количество страниц"
    ),
    author_ids: List[int] | None = Query(None, gt=0, description="Список ID авторов"),
    genre_ids: List[int] | None = Query(None, gt=0, description="Список ID жанров"),
    page: int = Query(1, gt=0, description="Номер страницы"),
    size: int = Query(20, gt=0, le=100, description="Количество элементов на странице"),
):
    """Возвращает отфильтрованный список книг с пагинацией"""
    statement = select(Book).distinct()

    if q:
        statement = statement.where(
            (col(Book.title).ilike(f"%{q}%")) | (col(Book.description).ilike(f"%{q}%"))
        )

    if min_page_count:
        statement = statement.where(Book.page_count >= min_page_count)

    if max_page_count:
        statement = statement.where(Book.page_count <= max_page_count)

    if author_ids:
        statement = statement.join(AuthorBookLink).where(
            AuthorBookLink.author_id.in_(  # ty: ignore[unresolved-attribute, unresolved-reference]
                author_ids
            )
        )

    if genre_ids:
        statement = statement.join(GenreBookLink).where(
            GenreBookLink.genre_id.in_(  # ty: ignore[unresolved-attribute, unresolved-reference]
                genre_ids
            )
        )

    total_statement = select(func.count()).select_from(statement.subquery())
    total = session.exec(total_statement).one()

    offset = (page - 1) * size
    statement = statement.offset(offset).limit(size)
    results = session.exec(statement).all()

    books_with_data = []
    for db_book in results:
        books_with_data.append(
            BookWithAuthorsAndGenres(
                **db_book.model_dump(),
                authors=[AuthorRead(**a.model_dump()) for a in db_book.authors],
                genres=[GenreRead(**g.model_dump()) for g in db_book.genres],
            )
        )

    return BookFilteredList(books=books_with_data, total=total)


@router.post(
    "/",
    response_model=BookRead,
    summary="Создать книгу",
    description="Добавляет книгу в систему",
)
def create_book(
    book: BookCreate,
    current_user: RequireStaff,
    session: Session = Depends(get_session),
):
    """Создает новую книгу в системе"""
    db_book = Book(**book.model_dump())
    session.add(db_book)
    session.commit()
    session.refresh(db_book)
    return BookRead(**db_book.model_dump())


@router.get(
    "/",
    response_model=BookList,
    summary="Получить список книг",
    description="Возвращает список всех книг в системе",
)
def read_books(session: Session = Depends(get_session)):
    """Возвращает список всех книг"""
    books = session.exec(select(Book)).all()
    return BookList(
        books=[BookRead(**book.model_dump()) for book in books], total=len(books)
    )


@router.get(
    "/{book_id}",
    response_model=BookWithAuthorsAndGenres,
    summary="Получить информацию о книге",
    description="Возвращает информацию о книге, её авторах и жанрах",
)
def get_book(
    book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
    session: Session = Depends(get_session),
):
    """Возвращает информацию о книге с авторами и жанрами"""
    book = session.get(Book, book_id)
    if not book:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Book not found"
        )

    authors = session.exec(
        select(Author).join(AuthorBookLink).where(AuthorBookLink.book_id == book_id)
    ).all()

    author_reads = [AuthorRead(**author.model_dump()) for author in authors]

    genres = session.exec(
        select(Genre).join(GenreBookLink).where(GenreBookLink.book_id == book_id)
    ).all()

    genre_reads = [GenreRead(**genre.model_dump()) for genre in genres]

    book_data = book.model_dump()
    book_data["authors"] = author_reads
    book_data["genres"] = genre_reads

    return BookWithAuthorsAndGenres(**book_data)


@router.put(
    "/{book_id}",
    response_model=Book,
    summary="Обновить информацию о книге",
    description="Обновляет информацию о книге в системе",
)
def update_book(
    current_user: RequireStaff,
    book_update: BookUpdate,
    book_id: int = Path(..., gt=0),
    session: Session = Depends(get_session),
):
    """Обновляет информацию о книге"""
    db_book = session.get(Book, book_id)
    if not db_book:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Book not found"
        )

    if book_update.status is not None:
        if book_update.status == BookStatus.BORROWED:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Статус 'borrowed' устанавливается только через выдачу книги",
            )

        if db_book.status == BookStatus.BORROWED:
            close_active_loan(session, book_id)

        db_book.status = book_update.status

    if book_update.title is not None or book_update.description is not None:
        if book_update.title is not None:
            db_book.title = book_update.title
        if book_update.description is not None:
            db_book.description = book_update.description

    session.add(db_book)
    session.commit()
    session.refresh(db_book)

    return BookRead(**db_book.model_dump())


@router.delete(
    "/{book_id}",
    response_model=BookRead,
    summary="Удалить книгу",
    description="Удаляет книгу их системы",
)
def delete_book(
    current_user: RequireStaff,
    book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
    session: Session = Depends(get_session),
):
    """Удаляет книгу из системы"""
    book = session.get(Book, book_id)
    if not book:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Book not found"
        )
    book_read = BookRead(
        id=(book.id or 0),
        title=book.title,
        description=book.description,
        status=book.status,
    )
    session.delete(book)
    session.commit()
    return book_read
