"""
Copyright: Ajatt-Tools and contributors; https://github.com/Ajatt-Tools
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
"""

import pathlib
import typing
from collections.abc import Sequence

from loguru import logger
from PyQt6.QtGui import QCursor, QPixmap, QScreen
from PyQt6.QtWidgets import QApplication

from zala.exceptions import ZalaException, CaptureScreenError
from zala.utils import generate_output_file_path


def repr_screen(screen: QScreen) -> str:
    """Return a human-readable string describing the screen's name, position, and size."""
    geometry = screen.geometry()
    top_left = geometry.topLeft()
    size = geometry.size()
    return f"Name {screen.name()}. Position {top_left.x(), top_left.y()}. Size {size.width()}x{size.height()}."


def debug_screens(screens: Sequence[QScreen]) -> None:
    """Log debug information about each available screen."""
    logger.debug(f"Found {len(screens)} screens.")
    for idx, screen in enumerate(screens):
        logger.debug(f"Screen #{idx}. {repr_screen(screen)}")


def grab_window(screen: QScreen) -> QPixmap:
    """
    Capture the entire screen contents and return as a pixmap scaled to the screen geometry.
    https://doc.qt.io/qt-6/qscreen.html#grabWindow
    """
    return screen.grabWindow(x=0, y=0).scaled(screen.geometry().size())


def find_screen_with_cursor(screens: Sequence[QScreen]) -> QScreen:
    """Find and return the screen that currently contains the mouse cursor."""
    cursor = QCursor.pos()  # Get the current position of the cursor
    # Iterate through the screens to find which one contains the cursor position
    logger.debug(f"Cursor is at {cursor.x(), cursor.y()}.")
    for screen in screens:
        if screen.geometry().contains(cursor):
            return screen
    raise RuntimeError("couldn't find active screen.")


class TakenScreenshot(typing.NamedTuple):
    """Result of capturing a screen, holding the pixmap and the source screen."""

    pixmap: QPixmap
    screen: QScreen


class ScreenshotSaveResult(typing.NamedTuple):
    """Result of saving a screenshot, indicating success and the file path used."""

    success: bool
    file_path: pathlib.Path


def save_screenshot(pixmap: QPixmap, output_file_path: str | None) -> ScreenshotSaveResult:
    """Save a pixmap to disk at the given path, or generate a default path if none is provided."""
    output_file_path = pathlib.Path(output_file_path) if output_file_path else generate_output_file_path()
    return ScreenshotSaveResult(pixmap.save(str(output_file_path)), output_file_path)


class ZalaScreenshot:
    """
    Take screenshots.
    """

    _app: QApplication

    def __init__(self, app: QApplication) -> None:
        """Initialize the screenshot helper with the running QApplication instance."""
        self._app = app

    def find_available_screens(self) -> list[QScreen]:
        """Return a list of all screens currently available to the application."""
        return self._app.screens()

    def capture_screen(self, index: int | None = None) -> TakenScreenshot:
        """Capture a screenshot from the screen at the given index, or the screen under the cursor if None."""
        screens = self.find_available_screens()
        debug_screens(screens)
        try:
            target_screen = screens[index]
        except TypeError:
            target_screen = find_screen_with_cursor(screens)
        except IndexError as e:
            raise CaptureScreenError(f"screen #{index} does not exist") from e
        pixmap = grab_window(target_screen)
        if pixmap.isNull():
            raise CaptureScreenError("failed to take screenshot. pixmap is null")
        logger.debug(f"Screen {target_screen.name()} taken.")
        return TakenScreenshot(pixmap, target_screen)
