from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, or_
from sqlalchemy.orm import selectinload
from typing import List, Optional
from uuid import UUID
import difflib # For similarity suggestions

from ...core.database import get_db
from ..dependencies import get_current_active_user
from ...models.user import User
from ...models.book import Book
from ...models.document import Document
from ...schemas.book import CreateBookRequest, UpdateBookRequest, BookResponse, BookSearchResponse, BookSuggestionResponse

router = APIRouter()

# Helper to convert Book ORM object to BookResponse
async def _get_book_response(db: AsyncSession, book: Book) -> BookResponse:
    document_count_result = await db.execute(
        select(func.count(Document.id)).where(Document.book_id == book.id)
    )
    document_count = document_count_result.scalar_one()
    return BookResponse(
        id=book.id,
        title=book.title,
        author=book.author,
        description=book.description,
        language=book.language,
        is_public=book.is_public,
        created_at=book.created_at,
        updated_at=book.updated_at,
        document_count=document_count
    )

@router.post("/", response_model=BookResponse, status_code=status.HTTP_201_CREATED)
async def create_book(
    book_data: CreateBookRequest,
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """새로운 서적 생성"""
    # Check if a book with the same title and author already exists for the user
    existing_book_query = select(Book).where(Book.title == book_data.title)
    if book_data.author:
        existing_book_query = existing_book_query.where(Book.author == book_data.author)
    
    existing_book = await db.execute(existing_book_query)
    if existing_book.scalar_one_or_none():
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="A book with this title and author already exists."
        )

    new_book = Book(**book_data.model_dump())
    db.add(new_book)
    await db.commit()
    await db.refresh(new_book)
    return await _get_book_response(db, new_book)

@router.get("/", response_model=List[BookResponse])
async def get_books(
    skip: int = 0,
    limit: int = 50,
    search: Optional[str] = Query(None, description="Search by book title or author"),
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """모든 서적 목록 조회"""
    query = select(Book)
    if search:
        query = query.where(
            or_(
                Book.title.ilike(f"%{search}%"),
                Book.author.ilike(f"%{search}%")
            )
        )
    
    # Only show public books or books owned by the current user/admin
    if not current_user.is_admin:
        query = query.where(Book.is_public == True) # For simplicity, assuming all books are public for now or user can only see public ones.
                                                    # In a real app, you'd link books to users.
    
    query = query.offset(skip).limit(limit).order_by(Book.title)
    result = await db.execute(query)
    books = result.scalars().all()

    response_books = []
    for book in books:
        response_books.append(await _get_book_response(db, book))
    return response_books

@router.get("/{book_id}", response_model=BookResponse)
async def get_book(
    book_id: UUID,
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """특정 서적 상세 정보 조회"""
    result = await db.execute(
        select(Book).where(Book.id == book_id)
    )
    book = result.scalar_one_or_none()

    if not book:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Book not found")
    
    # Access control (simplified)
    if not book.is_public and not current_user.is_admin:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions to access this book")

    return await _get_book_response(db, book)

@router.put("/{book_id}", response_model=BookResponse)
async def update_book(
    book_id: UUID,
    book_data: UpdateBookRequest,
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """서적 정보 업데이트"""
    if not current_user.is_admin: # Only admin can update books for now
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only administrators can update books")

    result = await db.execute(
        select(Book).where(Book.id == book_id)
    )
    book = result.scalar_one_or_none()

    if not book:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Book not found")

    for field, value in book_data.model_dump(exclude_unset=True).items():
        setattr(book, field, value)
    
    await db.commit()
    await db.refresh(book)
    return await _get_book_response(db, book)

@router.delete("/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_book(
    book_id: UUID,
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """서적 삭제"""
    if not current_user.is_admin: # Only admin can delete books for now
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only administrators can delete books")

    result = await db.execute(
        select(Book).where(Book.id == book_id)
    )
    book = result.scalar_one_or_none()

    if not book:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Book not found")
    
    # Disassociate documents from this book before deleting
    await db.execute(
        select(Document).where(Document.book_id == book_id)
    )
    documents_to_update = (await db.execute(select(Document).where(Document.book_id == book_id))).scalars().all()
    for doc in documents_to_update:
        doc.book_id = None
    
    await db.delete(book)
    await db.commit()
    return {"message": "Book deleted successfully"}

@router.get("/search/", response_model=List[BookSearchResponse])
async def search_books(
    q: str = Query(..., min_length=1, description="Search query for book title or author"),
    limit: int = Query(10, ge=1, le=100),
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """서적 검색 (제목 또는 저자)"""
    query = select(Book).where(
        or_(
            Book.title.ilike(f"%{q}%"),
            Book.author.ilike(f"%{q}%")
        )
    )
    if not current_user.is_admin:
        query = query.where(Book.is_public == True)
    
    result = await db.execute(query.limit(limit))
    books = result.scalars().all()

    response_books = []
    for book in books:
        response_books.append(await _get_book_response(db, book))
    return response_books

@router.get("/suggestions/", response_model=List[BookSuggestionResponse])
async def get_book_suggestions(
    title: str = Query(..., min_length=1, description="Book title for suggestions"),
    limit: int = Query(5, ge=1, le=10),
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db)
):
    """제목 유사도 기반 서적 추천"""
    all_books_query = select(Book)
    if not current_user.is_admin:
        all_books_query = all_books_query.where(Book.is_public == True)
    
    all_books_result = await db.execute(all_books_query)
    all_books = all_books_result.scalars().all()

    suggestions = []
    for book in all_books:
        # Calculate similarity score using difflib
        score = difflib.SequenceMatcher(None, title.lower(), book.title.lower()).ratio()
        if score > 0.1: # Only consider if there's some similarity
            suggestions.append({
                "book": book,
                "similarity_score": score
            })
    
    # Sort by similarity score in descending order
    suggestions.sort(key=lambda x: x["similarity_score"], reverse=True)
    
    response_suggestions = []
    for s in suggestions[:limit]:
        book_response = await _get_book_response(db, s["book"])
        response_suggestions.append(BookSuggestionResponse(
            **book_response.model_dump(),
            similarity_score=s["similarity_score"]
        ))
    return response_suggestions