"""
Main application controller for DReader e-reader application.

This module provides the DReaderApplication class which orchestrates:
- Library and reading mode transitions
- State persistence and recovery
- HAL integration for display and input
- Event routing and handling

The application uses asyncio for non-blocking operations and integrates
with a hardware abstraction layer (HAL) for platform independence.
"""

import asyncio
import logging
from pathlib import Path
from typing import Optional
from PIL import Image

from .library import LibraryManager
from .application import EbookReader
from .state import StateManager, EreaderMode, OverlayState, BookState
from .gesture import TouchEvent, GestureType, ActionType


logger = logging.getLogger(__name__)


class AppConfig:
    """
    Configuration for DReaderApplication.

    Attributes:
        display_hal: Hardware abstraction layer for display/input
        library_path: Path to directory containing EPUB files
        page_size: Tuple of (width, height) for rendered pages
        bookmarks_dir: Directory for bookmark storage (default: ~/.config/dreader/bookmarks)
        highlights_dir: Directory for highlights storage (default: ~/.config/dreader/highlights)
        state_file: Path to state JSON file (default: ~/.config/dreader/state.json)
        auto_save_interval: Seconds between automatic state saves (default: 60)
        force_library_mode: If True, always start in library mode (default: False)
        log_level: Logging level (default: logging.INFO)
    """

    def __init__(
        self,
        display_hal,
        library_path: str,
        page_size: tuple[int, int] = (800, 1200),
        bookmarks_dir: Optional[str] = None,
        highlights_dir: Optional[str] = None,
        state_file: Optional[str] = None,
        auto_save_interval: int = 60,
        force_library_mode: bool = False,
        log_level: int = logging.INFO
    ):
        self.display_hal = display_hal
        self.library_path = library_path
        self.page_size = page_size
        self.force_library_mode = force_library_mode

        # Set up default config paths
        config_dir = Path.home() / ".config" / "dreader"
        config_dir.mkdir(parents=True, exist_ok=True)

        self.bookmarks_dir = bookmarks_dir or str(config_dir / "bookmarks")
        self.highlights_dir = highlights_dir or str(config_dir / "highlights")
        self.state_file = state_file or str(config_dir / "state.json")
        self.auto_save_interval = auto_save_interval
        self.log_level = log_level


class DReaderApplication:
    """
    Main application controller coordinating library and reading modes.

    This class orchestrates all major components of the e-reader:
    - LibraryManager for book browsing
    - EbookReader for reading books
    - StateManager for persistence
    - DisplayHAL for hardware integration

    Usage:
        config = AppConfig(
            display_hal=MyDisplayHAL(),
            library_path="/path/to/books"
        )

        app = DReaderApplication(config)
        await app.start()

        # In event loop:
        await app.handle_touch(touch_event)

        await app.shutdown()
    """

    def __init__(self, config: AppConfig):
        """
        Initialize the application with configuration.

        Args:
            config: Application configuration
        """
        self.config = config

        # Set up logging
        logging.basicConfig(level=config.log_level)
        logger.info("Initializing DReaderApplication")

        # State management
        self.state_manager = StateManager(
            state_file=config.state_file,
            auto_save_interval=config.auto_save_interval
        )
        self.state = self.state_manager.load_state()
        logger.info(f"Loaded state: mode={self.state.mode}, current_book={self.state.current_book}")

        # Components (lazy-initialized)
        self.library: Optional[LibraryManager] = None
        self.reader: Optional[EbookReader] = None

        # Display abstraction
        self.display_hal = config.display_hal
        self.current_image: Optional[Image.Image] = None

        # Running state
        self.running = False

    async def start(self):
        """
        Start the application and display initial screen.

        This method:
        1. Starts automatic state saving
        2. Restores previous mode or shows library
        3. Displays the initial screen
        """
        logger.info("Starting DReaderApplication")
        self.running = True

        # Start auto-save
        self.state_manager.start_auto_save()
        logger.info(f"Auto-save started (interval: {self.config.auto_save_interval}s)")

        # Restore previous mode (or force library mode if configured)
        force_library = getattr(self.config, 'force_library_mode', False)

        if force_library:
            logger.info("Force library mode enabled - starting in library")
            await self._enter_library_mode()
        elif self.state.mode == EreaderMode.READING and self.state.current_book:
            logger.info(f"Resuming reading mode: {self.state.current_book.path}")
            await self._enter_reading_mode(self.state.current_book.path)
        else:
            logger.info("Entering library mode")
            await self._enter_library_mode()

        # Display initial screen
        await self._update_display()
        logger.info("Application started successfully")

    async def shutdown(self):
        """
        Gracefully shutdown the application.

        This method:
        1. Saves current reading position
        2. Closes active components
        3. Stops auto-save and saves final state
        """
        logger.info("Shutting down DReaderApplication")
        self.running = False

        # Save current position if reading
        if self.reader and self.reader.is_loaded():
            logger.info("Saving auto-resume position")
            self.reader.save_position("__auto_resume__")
            self.reader.close()

        # Clean up library
        if self.library:
            self.library.cleanup()

        # Stop auto-save and save final state
        await self.state_manager.stop_auto_save(save_final=True)
        logger.info("Application shutdown complete")

    async def handle_touch(self, event: TouchEvent):
        """
        Process touch event based on current mode.

        Args:
            event: Touch event from HAL
        """
        logger.info(f"[APP] Received touch event: {event.gesture.value} at ({event.x}, {event.y}), mode={self.state.mode.value}")

        if self.state.mode == EreaderMode.LIBRARY:
            logger.info("[APP] Routing to library touch handler")
            await self._handle_library_touch(event)
        elif self.state.mode == EreaderMode.READING:
            logger.info("[APP] Routing to reading touch handler")
            await self._handle_reading_touch(event)

        # Update display after handling
        await self._update_display()

    async def _enter_library_mode(self):
        """
        Switch to library browsing mode.

        This method:
        1. Saves and closes reader if active
        2. Initializes library manager
        3. Renders library view
        4. Updates state
        """
        logger.info("Entering library mode")

        # Save and close reader if active
        if self.reader:
            if self.reader.is_loaded():
                logger.info("Saving reading position before closing")
                self.reader.save_position("__auto_resume__")
            self.reader.close()
            self.reader = None

        # Initialize library if needed
        if not self.library:
            logger.info(f"Initializing library manager: {self.config.library_path}")
            self.library = LibraryManager(
                library_path=self.config.library_path,
                page_size=self.config.page_size,
                cache_dir=None  # Uses default ~/.config/dreader
            )

        # Scan for books (async operation)
        logger.info("Scanning library for books")
        books = self.library.scan_library()
        logger.info(f"Found {len(books)} books")

        # Render library view
        logger.info("Rendering library view")
        self.current_image = self.library.render_library()

        # Update state
        self.state_manager.set_mode(EreaderMode.LIBRARY)
        logger.info("Library mode active")

    async def _enter_reading_mode(self, book_path: str):
        """
        Switch to reading mode.

        Args:
            book_path: Path to EPUB file to open

        This method:
        1. Initializes reader if needed
        2. Loads the book
        3. Applies saved settings
        4. Restores reading position
        5. Updates state
        6. Renders first/current page
        """
        logger.info(f"Entering reading mode: {book_path}")

        # Verify book exists
        if not Path(book_path).exists():
            logger.error(f"Book not found: {book_path}")
            # Return to library
            await self._enter_library_mode()
            return

        # Initialize reader if needed
        if not self.reader:
            logger.info("Initializing ebook reader")
            self.reader = EbookReader(
                page_size=self.config.page_size,
                margin=40,
                background_color=(255, 255, 255),
                bookmarks_dir=self.config.bookmarks_dir,
                highlights_dir=self.config.highlights_dir
            )

        # Load book
        logger.info(f"Loading EPUB: {book_path}")
        success = self.reader.load_epub(book_path)

        if not success:
            logger.error(f"Failed to load EPUB: {book_path}")
            # Return to library
            await self._enter_library_mode()
            return

        logger.info(f"Loaded: {self.reader.book_title} by {self.reader.book_author}")

        # Apply saved settings
        logger.info("Applying saved settings")
        settings_dict = self.state.settings.to_dict()
        self.reader.apply_settings(settings_dict)

        # Restore position
        logger.info("Restoring reading position")
        position_loaded = self.reader.load_position("__auto_resume__")
        if position_loaded:
            pos_info = self.reader.get_position_info()
            logger.info(f"Resumed at position: {pos_info}")
        else:
            logger.info("No saved position, starting from beginning")

        # Update state
        self.state_manager.set_current_book(BookState(
            path=book_path,
            title=self.reader.book_title or "Unknown",
            author=self.reader.book_author or "Unknown"
        ))
        self.state_manager.set_mode(EreaderMode.READING)

        # Render current page
        logger.info("Rendering current page")
        self.current_image = self.reader.get_current_page()

        logger.info("Reading mode active")

    async def _handle_library_touch(self, event: TouchEvent):
        """
        Handle touch events in library mode.

        Args:
            event: Touch event
        """
        if event.gesture == GestureType.TAP:
            logger.debug(f"Library tap at ({event.x}, {event.y})")

            # Check if a book was selected
            book_path = self.library.handle_library_tap(event.x, event.y)

            if book_path:
                logger.info(f"Book selected: {book_path}")
                await self._enter_reading_mode(book_path)
            else:
                logger.debug("Tap did not hit a book")

    async def _handle_reading_touch(self, event: TouchEvent):
        """
        Handle touch events in reading mode.

        Args:
            event: Touch event
        """
        # Delegate to reader's gesture handler
        logger.info(f"[APP] Calling reader.handle_touch({event.gesture.value})")
        response = self.reader.handle_touch(event)

        # response.action is already a string (ActionType enum value), not the enum itself
        logger.info(f"[APP] Reader response: action={response.action}, data={response.data}")

        # Handle special actions
        if response.action == ActionType.BACK_TO_LIBRARY:
            logger.info("[APP] → Returning to library")
            await self._enter_library_mode()

        elif response.action == ActionType.PAGE_TURN:
            logger.info(f"[APP] → Page turned: {response.data}")
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.OVERLAY_OPENED:
            logger.info(f"[APP] → Overlay opened: {self.reader.get_overlay_state()}")
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.OVERLAY_CLOSED:
            logger.info("[APP] → Overlay closed")
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.SETTING_CHANGED:
            logger.info(f"[APP] → Setting changed: {response.data}")
            # Update state with new settings
            settings = self.reader.get_current_settings()
            self.state_manager.update_settings(settings)
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.CHAPTER_SELECTED:
            logger.info(f"[APP] → Chapter selected: {response.data}")
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.BOOKMARK_SELECTED:
            logger.info(f"[APP] → Bookmark selected: {response.data}")
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.NAVIGATE:
            logger.debug("Navigation action")
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.ZOOM:
            logger.info(f"Zoom action: {response.data}")
            # Font size changed
            settings = self.reader.get_current_settings()
            self.state_manager.update_settings(settings)
            self.current_image = self.reader.get_current_page()

        elif response.action == ActionType.ERROR:
            logger.error(f"Error: {response.data}")

    async def _update_display(self):
        """
        Update the display with current image.

        This method sends the current image to the HAL for display.
        """
        if self.current_image:
            logger.debug(f"Updating display: {self.current_image.size}")
            await self.display_hal.show_image(self.current_image)
        else:
            logger.warning("No image to display")

    def get_current_mode(self) -> EreaderMode:
        """Get current application mode."""
        return self.state.mode

    def get_overlay_state(self) -> OverlayState:
        """Get current overlay state (only valid in reading mode)."""
        if self.reader:
            return self.reader.get_overlay_state()
        return OverlayState.NONE

    def is_running(self) -> bool:
        """Check if application is running."""
        return self.running
