"""
Library manager for browsing and selecting books.

Handles:
- Scanning directories for EPUB files
- Extracting and caching book metadata and covers
- Rendering interactive library view using pyWebLayout
- Processing tap/click events to select books
"""

from __future__ import annotations
import os
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from PIL import Image, ImageDraw
import tempfile
import base64
from io import BytesIO

from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.concrete.table import TableRenderer, TableStyle
from pyWebLayout.abstract.block import Table
from pyWebLayout.abstract.interactive_image import InteractiveImage
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style.fonts import Font
from pyWebLayout.core.query import QueryResult

from .book_utils import scan_book_directory, extract_book_metadata
from .state import LibraryState


class LibraryManager:
    """
    Manages the book library view and interactions.

    Features:
    - Scan EPUB directories
    - Cache book metadata and covers
    - Render interactive library table
    - Handle tap events to select books
    """

    def __init__(
        self,
        library_path: str,
        cache_dir: Optional[str] = None,
        page_size: Tuple[int, int] = (800, 1200),
        books_per_page: int = 6
    ):
        """
        Initialize library manager.

        Args:
            library_path: Path to directory containing EPUB files
            cache_dir: Optional cache directory for covers. If None, uses default.
            page_size: Page size for library view rendering
            books_per_page: Number of books to display per page (must be even for 2-column layout, default: 6)
        """
        self.library_path = Path(library_path)
        self.page_size = page_size
        self.books_per_page = books_per_page if books_per_page % 2 == 0 else books_per_page + 1

        # Set cache directory
        if cache_dir:
            self.cache_dir = Path(cache_dir)
        else:
            self.cache_dir = self._get_default_cache_dir()

        self.cache_dir.mkdir(parents=True, exist_ok=True)
        self.covers_dir = self.cache_dir / 'covers'
        self.covers_dir.mkdir(exist_ok=True)

        # Current library state
        self.books: List[Dict] = []
        self.library_table: Optional[Table] = None
        self.rendered_page: Optional[Page] = None
        self.temp_cover_files: List[str] = []  # Track temp files for cleanup
        self.row_bounds: List[Tuple[int, int, int, int]] = []  # Bounding boxes for rows (x, y, w, h)
        self.table_renderer: Optional[TableRenderer] = None  # Store renderer for bounds info
        self.current_page: int = 0  # Current page index for pagination

    @staticmethod
    def _get_default_cache_dir() -> Path:
        """Get default cache directory based on platform"""
        if os.name == 'nt':  # Windows
            config_dir = Path(os.environ.get('APPDATA', '~/.config'))
        else:  # Linux/Mac
            config_dir = Path.home() / '.config'

        return config_dir / 'dreader'

    def scan_library(self, force_refresh: bool = False) -> List[Dict]:
        """
        Scan library directory for EPUB files and extract metadata.

        Args:
            force_refresh: If True, re-scan even if cache exists

        Returns:
            List of book dictionaries with metadata
        """
        print(f"Scanning library: {self.library_path}")

        if not self.library_path.exists():
            print(f"Library path does not exist: {self.library_path}")
            return []

        # Scan directory
        self.books = scan_book_directory(self.library_path)

        # Cache covers to disk if not already cached
        for book in self.books:
            self._cache_book_cover(book)

        print(f"Found {len(self.books)} books in library")
        return self.books

    def _cache_book_cover(self, book: Dict) -> Optional[str]:
        """
        Cache book cover image to disk.

        Args:
            book: Book dictionary with cover_data (base64) or path

        Returns:
            Path to cached cover file, or None if no cover
        """
        if not book.get('cover_data'):
            return None

        # Generate cache filename from book path
        book_path = Path(book['path'])
        cover_filename = f"{book_path.stem}_cover.png"
        cover_path = self.covers_dir / cover_filename

        # Skip if already cached
        if cover_path.exists():
            book['cover_path'] = str(cover_path)
            return str(cover_path)

        try:
            # Decode base64 and save to cache
            img_data = base64.b64decode(book['cover_data'])
            img = Image.open(BytesIO(img_data))
            img.save(cover_path, 'PNG')

            book['cover_path'] = str(cover_path)
            print(f"Cached cover: {cover_filename}")
            return str(cover_path)

        except Exception as e:
            print(f"Error caching cover for {book['title']}: {e}")
            return None

    def create_library_table(self, books: Optional[List[Dict]] = None, page: Optional[int] = None) -> Table:
        """
        Create interactive library table with book covers and info in 2-column grid.

        Args:
            books: List of books to display. If None, uses self.books
            page: Page number to display (0-indexed). If None, uses self.current_page

        Returns:
            Table object ready for rendering
        """
        if books is None:
            books = self.books

        if page is None:
            page = self.current_page

        if not books:
            print("No books to display in library")
            books = []

        # Calculate pagination
        total_pages = (len(books) + self.books_per_page - 1) // self.books_per_page
        start_idx = page * self.books_per_page
        end_idx = min(start_idx + self.books_per_page, len(books))
        page_books = books[start_idx:end_idx]

        print(f"Creating library table with {len(page_books)} books (page {page + 1}/{total_pages})...")

        # Create table with caption showing page info
        caption_text = f"My Library (Page {page + 1}/{total_pages})" if total_pages > 1 else "My Library"
        table = Table(caption=caption_text, style=Font(font_size=18, weight="bold"))

        # Add books in 2-column grid (each pair of books gets 2 rows: covers then details)
        for i in range(0, len(page_books), 2):
            # Row 1: Covers for this pair
            cover_row = table.create_row("body")

            # Add first book's cover (left column)
            self._add_book_cover(cover_row, page_books[i])

            # Add second book's cover (right column) if it exists
            if i + 1 < len(page_books):
                self._add_book_cover(cover_row, page_books[i + 1])
            else:
                # Add empty cell if odd number of books
                cover_row.create_cell()

            # Row 2: Details for this pair
            details_row = table.create_row("body")

            # Add first book's details (left column)
            self._add_book_details(details_row, page_books[i])

            # Add second book's details (right column) if it exists
            if i + 1 < len(page_books):
                self._add_book_details(details_row, page_books[i + 1])
            else:
                # Add empty cell if odd number of books
                details_row.create_cell()

        self.library_table = table
        return table

    def _add_book_cover(self, row, book: Dict):
        """
        Add a book cover to a table row.

        Args:
            row: Table row to add cover to
            book: Book dictionary with metadata
        """
        cover_cell = row.create_cell()

        cover_path = book.get('cover_path')
        book_path = book['path']

        # Create callback that returns book path
        callback = lambda point, path=book_path: path

        # Add cover image
        if cover_path and Path(cover_path).exists():
            # Use cached cover with callback
            img = InteractiveImage.create_and_add_to(
                cover_cell,
                source=cover_path,
                alt_text=book['title'],
                callback=callback
            )
        elif book.get('cover_data'):
            # Decode base64 and save to temp file for InteractiveImage
            try:
                img_data = base64.b64decode(book['cover_data'])
                img = Image.open(BytesIO(img_data))

                # Save to temp file
                with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
                    img.save(tmp.name, 'PNG')
                    temp_path = tmp.name
                    self.temp_cover_files.append(temp_path)

                img = InteractiveImage.create_and_add_to(
                    cover_cell,
                    source=temp_path,
                    alt_text=book['title'],
                    callback=callback
                )
            except Exception as e:
                print(f"Error creating cover image for {book['title']}: {e}")
                self._add_no_cover_text(cover_cell)
        else:
            # No cover available
            self._add_no_cover_text(cover_cell)

    def _add_book_details(self, row, book: Dict):
        """
        Add book details (title, author, filename) to a table row.

        Args:
            row: Table row to add details to
            book: Book dictionary with metadata
        """
        details_cell = row.create_cell()

        # Title paragraph
        title_para = details_cell.create_paragraph()
        for word in book['title'].split():
            title_para.add_word(Word(word, Font(font_size=14, weight="bold")))

        # Author paragraph
        author_para = details_cell.create_paragraph()
        for word in book.get('author', 'Unknown').split():
            author_para.add_word(Word(word, Font(font_size=12)))

        # Filename paragraph (small, gray)
        filename_para = details_cell.create_paragraph()
        filename_para.add_word(Word(
            Path(book['path']).name,
            Font(font_size=10, colour=(150, 150, 150))
        ))

    def _add_no_cover_text(self, cell):
        """Add placeholder text when no cover is available"""
        para = cell.create_paragraph()
        para.add_word(Word("[No", Font(font_size=10, colour=(128, 128, 128))))
        para.add_word(Word("cover]", Font(font_size=10, colour=(128, 128, 128))))

    def render_library(self, table: Optional[Table] = None) -> Image.Image:
        """
        Render the library table to an image.

        Args:
            table: Table to render. If None, uses self.library_table

        Returns:
            PIL Image of the rendered library
        """
        if table is None:
            if self.library_table is None:
                print("No table to render, creating one first...")
                self.create_library_table()
            table = self.library_table

        print("Rendering library table...")

        # Create page
        page_style = PageStyle(
            border_width=0,
            padding=(30, 30, 30, 30),
            background_color=(255, 255, 255)
        )

        page = Page(size=self.page_size, style=page_style)
        canvas = page.render()
        draw = ImageDraw.Draw(canvas)

        # Table style
        table_style = TableStyle(
            border_width=1,
            border_color=(200, 200, 200),
            cell_padding=(10, 15, 10, 15),
            header_bg_color=(240, 240, 240),
            cell_bg_color=(255, 255, 255),
            alternate_row_color=(250, 250, 250)
        )

        # Position table
        table_origin = (page_style.padding[3], page_style.padding[0])
        table_width = page.size[0] - page_style.padding[1] - page_style.padding[3]

        # Render table with canvas support for images
        self.table_renderer = TableRenderer(
            table,
            table_origin,
            table_width,
            draw,
            table_style,
            canvas  # Pass canvas to enable image rendering
        )
        self.table_renderer.render()

        # Store rendered page for query support
        self.rendered_page = page

        return canvas

    def handle_library_tap(self, x: int, y: int) -> Optional[str]:
        """
        Handle tap event on library view with 2-column grid.

        The layout has alternating rows: cover rows and detail rows.
        Each pair of rows (cover + detail) represents one pair of books (2 books).
        Tapping on either the cover row or detail row selects the corresponding book.

        Args:
            x: X coordinate of tap
            y: Y coordinate of tap

        Returns:
            Path to selected book, or None if no book tapped
        """
        if not self.library_table or not self.table_renderer:
            print("No library table available")
            return None

        try:
            # Get paginated books for current page
            start_idx = self.current_page * self.books_per_page
            end_idx = min(start_idx + self.books_per_page, len(self.books))
            page_books = self.books[start_idx:end_idx]

            # Build a mapping of row sections in order
            all_rows = list(self.library_table.all_rows())

            # Find which row was tapped by checking row renderers
            for row_idx, row_renderer in enumerate(self.table_renderer._row_renderers):
                # Get the row renderer's bounds
                row_x, row_y = row_renderer._origin
                row_w, row_h = row_renderer._size

                # Check if tap is within this row's bounds
                if (row_x <= x <= row_x + row_w and
                    row_y <= y <= row_y + row_h):

                    # Get the section and row for this renderer index
                    if row_idx < len(all_rows):
                        section, row = all_rows[row_idx]

                        # Only handle body rows
                        if section == "body":
                            # Find which body row this is (0-indexed)
                            body_row_index = sum(1 for s, _ in all_rows[:row_idx] if s == "body")

                            # Each pair of books uses 2 rows (cover row + detail row)
                            # Determine which book pair this row belongs to
                            book_pair_index = body_row_index // 2  # Which pair of books (0, 1, 2, ...)
                            is_cover_row = body_row_index % 2 == 0  # Even rows are covers, odd are details

                            # Check cell renderers in this row
                            if hasattr(row_renderer, '_cell_renderers') and len(row_renderer._cell_renderers) >= 1:
                                # Check left cell (first book in pair)
                                left_cell = row_renderer._cell_renderers[0]
                                left_x, left_y = left_cell._origin
                                left_w, left_h = left_cell._size

                                if (left_x <= x <= left_x + left_w and
                                    left_y <= y <= left_y + left_h):
                                    # Left column (first book in pair)
                                    book_index = book_pair_index * 2
                                    if book_index < len(page_books):
                                        book_path = page_books[book_index]['path']
                                        row_type = "cover" if is_cover_row else "detail"
                                        print(f"Book selected (pair {book_pair_index}, left {row_type}): {book_path}")
                                        return book_path

                                # Check right cell (second book in pair) if it exists
                                if len(row_renderer._cell_renderers) >= 2:
                                    right_cell = row_renderer._cell_renderers[1]
                                    right_x, right_y = right_cell._origin
                                    right_w, right_h = right_cell._size

                                    if (right_x <= x <= right_x + right_w and
                                        right_y <= y <= right_y + right_h):
                                        # Right column (second book in pair)
                                        book_index = book_pair_index * 2 + 1
                                        if book_index < len(page_books):
                                            book_path = page_books[book_index]['path']
                                            row_type = "cover" if is_cover_row else "detail"
                                            print(f"Book selected (pair {book_pair_index}, right {row_type}): {book_path}")
                                            return book_path

            print(f"No book tapped at ({x}, {y})")
            return None

        except Exception as e:
            print(f"Error handling library tap: {e}")
            import traceback
            traceback.print_exc()
            return None

    def get_book_at_index(self, index: int) -> Optional[Dict]:
        """
        Get book by index in library.

        Args:
            index: Book index

        Returns:
            Book dictionary or None
        """
        if 0 <= index < len(self.books):
            return self.books[index]
        return None

    def next_page(self) -> bool:
        """
        Navigate to next page of library.

        Returns:
            True if page changed, False if already on last page
        """
        total_pages = (len(self.books) + self.books_per_page - 1) // self.books_per_page
        if self.current_page < total_pages - 1:
            self.current_page += 1
            return True
        return False

    def previous_page(self) -> bool:
        """
        Navigate to previous page of library.

        Returns:
            True if page changed, False if already on first page
        """
        if self.current_page > 0:
            self.current_page -= 1
            return True
        return False

    def set_page(self, page: int) -> bool:
        """
        Set current page.

        Args:
            page: Page number (0-indexed)

        Returns:
            True if page changed, False if invalid page
        """
        total_pages = (len(self.books) + self.books_per_page - 1) // self.books_per_page
        if 0 <= page < total_pages:
            self.current_page = page
            return True
        return False

    def get_total_pages(self) -> int:
        """
        Get total number of pages.

        Returns:
            Total number of pages
        """
        return (len(self.books) + self.books_per_page - 1) // self.books_per_page

    def get_library_state(self) -> LibraryState:
        """
        Get current library state for persistence.

        Returns:
            LibraryState object
        """
        return LibraryState(
            books_path=str(self.library_path),
            last_selected_index=0,  # TODO: Track last selection
            scan_cache=[
                {
                    'path': book['path'],
                    'title': book['title'],
                    'author': book.get('author', 'Unknown'),
                    'cover_cached': bool(book.get('cover_path'))
                }
                for book in self.books
            ]
        )

    def cleanup(self):
        """Clean up temporary files"""
        for temp_file in self.temp_cover_files:
            try:
                if os.path.exists(temp_file):
                    os.unlink(temp_file)
            except Exception as e:
                print(f"Error cleaning up temp file {temp_file}: {e}")
        self.temp_cover_files.clear()

    def __del__(self):
        """Destructor to ensure cleanup"""
        self.cleanup()
