# Standard
from pathlib import Path
from typing import Callable

# PyQGIS
from qgis.core import Qgis
from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QPushButton, QWidget

from oslandia.gui.stylesheet import WidgetStyleSheet

# Project
from oslandia.toolbelt.log_handler import PlgLogger


class NavigationStackedWidget(WidgetStyleSheet):
    """Custom widget that allows adding and removing pages in a QStackWidget and
    associating a button with each page. These buttons are used for navigation."""

    def __init__(self, parent: QWidget | None = None) -> None:
        """Initialize the widget.

        :param parent: parent widget, defaults to None
        :type parent: Optional[QWidget], optional
        """
        # Initialize module and UI
        super().__init__(parent)
        uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self)

        self.btn_layout.setSpacing(3)
        self.btn_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)

        # Logger
        self.log = PlgLogger().log

        # Store page access conditions: {index: callable_condition}
        self.page_conditions: dict[int, Callable[[], bool]] = {}

    def add_page(
        self, page_widget: QWidget, can_access: Callable[[], bool] | None = None
    ) -> None:
        """Add a page with an optional access condition.

        :param page_widget: Widget displayed on the page
        :type page_widget: QWidget
        :param can_access: Optional function that returns True if the page is accessible
        :type can_access: Optional[callable]
        """

        # Add page
        page_index = self.stacked_widget.count()
        self.stacked_widget.addWidget(page_widget)

        # Store condition if provided
        if can_access is not None:
            self.page_conditions[page_index] = can_access

        # Create button with fixed size and no text for a progress bar effect
        btn = QPushButton()
        btn.setCursor(Qt.CursorShape.PointingHandCursor)
        btn.setFixedSize(55, 8)
        btn.setProperty("class", "nav-button")

        # Store widget on button data
        btn.setProperty("widget", page_widget)
        btn.setProperty("page_index", page_index)

        self.btn_layout.addWidget(btn, alignment=Qt.AlignmentFlag.AlignCenter)
        btn.clicked.connect(self._btn_clicked)

        # Connect navigation signals
        page_widget.next_page_signal.connect(self.next_page)
        page_widget.previous_page_signal.connect(self.previous_page)

        # Update all button styles after page creation
        self._update_button_styles()

    def is_page_accessible(self, index: int) -> bool:
        """Check whether a page is accessible.

        :param index: Page index
        :type index: int

        :return: True if accessible, False otherwise
        :rtype: bool
        """
        # If no condition is defined, the page is accessible
        if index not in self.page_conditions:
            return True

        # Evaluate the condition
        try:
            return self.page_conditions[index]()
        except Exception as e:
            self.log(
                message=self.tr("Error evaluating condition for page {0}: {1}").format(
                    index, str(e)
                ),
                log_level=Qgis.MessageLevel.Warning,
            )
            return False

    def _btn_clicked(self) -> None:
        """Manage navigation between pages when clicking on associated buttons."""
        btn = self.sender()
        page_index = btn.property("page_index")

        # Check if page is accessible
        if not self.is_page_accessible(page_index):
            self.log(
                message=self.tr("This page is not accessible."),
                log_level=Qgis.MessageLevel.Warning,
                duration=5,
                push=True,
            )
            return

        widget = btn.property("widget")
        self.stacked_widget.setCurrentWidget(widget)
        self._update_button_styles()

    def _update_button_styles(self) -> None:
        """Update the styles of all buttons according to the active page."""
        current_index = self.stacked_widget.currentIndex()

        for i in range(self.btn_layout.count()):
            btn = self.btn_layout.itemAt(i).widget()
            page_index = btn.property("page_index")

            is_active = page_index == current_index
            is_accessible = self.is_page_accessible(page_index)

            self._set_button_style(btn, active=is_active, accessible=is_accessible)

    def _set_button_style(
        self, btn: QPushButton, active: bool, accessible: bool = True
    ) -> None:
        """Apply the style to a button based on its state.

        :param btn: Button
        :type btn: QPushButton
        :param active: Whether the button is active
        :type active: bool
        :param accessible: Whether the page is accessible (not locked by a condition)
        :type accessible: bool
        """
        if not accessible:
            # Not accessible: gray and disabled
            btn.setStyleSheet(
                """
                QPushButton {
                    background-color: #cccccc;
                    color: #888888;
                }
            """
            )
            btn.setEnabled(False)
        elif active:
            # Active button: Oslandia blue
            btn.setStyleSheet(
                """
                QPushButton {
                    background-color: #3a74ac;
                    color: white;
                }
                QPushButton:hover {
                    background-color: #2a557e;
                }
            """
            )
            btn.setEnabled(True)
        else:
            # Inactive button: white
            btn.setStyleSheet(
                """
                QPushButton {
                    background-color: white;
                }
                QPushButton:hover {
                    background-color: #f0f0f0;
                }
            """
            )
            btn.setEnabled(True)

    def set_current_page(self, index: int) -> None:
        """Set the current page.

        :param index: Page index
        :type index: int
        """
        if 0 <= index < self.stacked_widget.count():
            # Check if page is accessible
            if not self.is_page_accessible(index):
                self.log(
                    message=self.tr("Cannot navigate to locked page {0}").format(index),
                    log_level=Qgis.MessageLevel.Warning,
                )
                return

            self.stacked_widget.setCurrentIndex(index)
            self._update_button_styles()
        else:
            self.log(
                self.tr("Invalid index {0}. Number of pages: {1}").format(
                    index, self.stacked_widget.count()
                ),
                log_level=Qgis.MessageLevel.Warning,
            )

    def refresh_accessibility(self) -> None:
        """Refresh the accessibility of all pages.

        Call this method when access conditions change.
        """
        self._update_button_styles()

    def next_page(self, skip: int = 0) -> None:
        """Navigate to the next page.

        If already on the last page, remains on it.
        Locked pages are skipped automatically.

        :param skip: Number of pages to skip (0 = next page, 1 = skip one page, etc.)
        :type skip: int
        :raises ValueError: If skip is negative.
        """
        if skip < 0:
            raise ValueError("skip must be >= 0")

        current_index = self.stacked_widget.currentIndex()
        total_pages = self.stacked_widget.count()
        next_index = current_index + 1 + skip

        while next_index < total_pages and not self.is_page_accessible(next_index):
            next_index += 1

        self.set_current_page(min(next_index, total_pages - 1))

    def previous_page(self, skip: int = 0) -> None:
        """Navigate to the previous page.

        If already on the first page, remains on it.
        Locked pages are skipped automatically.

        :param skip: Number of pages to skip backwards
                    (0 = previous page, 1 = skip one page back, etc.)
        :type skip: int
        :raises ValueError: If skip is negative.
        """
        if skip < 0:
            raise ValueError("skip must be >= 0")

        current_index = self.stacked_widget.currentIndex()
        previous_index = current_index - 1 - skip

        while previous_index >= 0 and not self.is_page_accessible(previous_index):
            previous_index -= 1

        self.set_current_page(max(0, previous_index))
