# standard
from pathlib import Path

# qgis
from qgis.core import QgsApplication
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QSize, Qt
from qgis.PyQt.QtGui import QColor, QIcon, QPixmap
from qgis.PyQt.QtWidgets import QGridLayout, QSizePolicy, QTableWidgetItem, QWidget

# project
from oslandia.__about__ import DIR_PLUGIN_ROOT
from oslandia.datamodels.mdl_projects_browser import ProjectObj, ProjectsObj
from oslandia.gui.showcase.task_download_showcase_assets import (
    ProjectAsset,
    ProjectAssetsDownloadTask,
)
from oslandia.gui.showcase.wdg_cards_project import ProjectCardsWidget
from oslandia.gui.stylesheet import DialogStyleSheet
from oslandia.showcase.showcase_manager import ShowcaseManager
from oslandia.toolbelt import PlgLogger
from oslandia.toolbelt.cache_manager import CacheManager
from oslandia.toolbelt.preferences import PlgOptionsManager


class Showcase(DialogStyleSheet):
    def __init__(self, parent: QWidget | None = None) -> None:
        # init module and ui
        super().__init__(parent)
        uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self)
        self.log = PlgLogger().log
        self.plugin_manager = ShowcaseManager()
        # icon
        self.setWindowIcon(
            QIcon(str(DIR_PLUGIN_ROOT.joinpath("resources/images/plugin.png")))
        )

        # cache
        self.cache_manager = CacheManager(".oslandia", "cache")

        # Data
        self.qgis_projects_obj = ProjectsObj(self.cache_manager.get_cache_path)
        self.plugins = self.qgis_projects_obj.get_projects("plugin")
        self.qgis = self.qgis_projects_obj.get_projects("qgis")
        self.others = self.qgis_projects_obj.get_projects("other")

        # list for the incremental updating of assets (logos and screenshots)
        self._table_items_by_project = {}
        self._card_widgets_by_project = {}
        self.download_task = None

        self.table_projects.itemSelectionChanged.connect(self.display_informations)
        self.table_projects.setIconSize(QSize(25, 25))
        self.populate_table()
        self.change_stack_buttons()

        if not PlgOptionsManager.get_plg_settings().debug_mode:
            self.lbl_date_refresh_json.setVisible(False)
            self.tb_refresh_json.setVisible(False)
        else:
            self.lbl_date_refresh_json.setText(
                "Information last updated: "
                + self.plugin_manager.get_date_json_projects_update
            )

            # Action buttons
            self.tb_refresh_json.clicked.connect(self.force_update_json)

            # Icon buttons
            self.tb_refresh_json.setIcon(
                QIcon(QgsApplication.iconPath("mActionRefresh.svg"))
            )

        # make the dialog a window to allow resizing
        self.setWindowFlags(Qt.WindowType.Window)

        # Cards
        self.add_cards_to_grid(self.grid, self.plugins)
        self.add_cards_to_grid(self.grid2, self.qgis)
        self.add_cards_to_grid(self.grid3, self.others)

        # Apply the last view
        self.stack.setCurrentIndex(
            PlgOptionsManager.get_plg_settings().project_browser_last_view
        )

        # Progressive downloading of missing or out-of-date icons/screenshots
        self._start_assets_download()

    def add_cards_to_grid(self, grid: QGridLayout, projects: list[ProjectObj]) -> None:
        """Add a list of projects to the table_projects.

        :param projects: List of ProjectObj to add.
        :param source: Source of the projects, used for the 'WhatsThis' field.
        """

        columns = 3
        for index, project in enumerate(projects):
            row = index // columns
            col = index % columns
            self.log(message=f"Displaying card for project: {project.name}")
            widget = ProjectCardsWidget(self, project.about)
            widget.set_display_data(project)
            widget.setSizePolicy(
                QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
            )
            grid.addWidget(widget, row, col)
            grid.setColumnStretch(col, 1)
            self._card_widgets_by_project.setdefault(project.name, []).append(widget)

    def change_stack_buttons(self) -> None:
        """Adds icons and actions to the buttons used to change pages in the stack."""
        buttons = [
            (self.tb_view_list, "view_list.svg", 1),
            (self.tb_view_cards, "view_cards.svg", 0),
        ]

        for button, icon_name, index in buttons:
            button.setIcon(
                QIcon(str(DIR_PLUGIN_ROOT / f"resources/images/{icon_name}"))
            )

            button.clicked.connect(lambda _, i=index: self.stack.setCurrentIndex(i))
            button.clicked.connect(
                lambda _, i=index: PlgOptionsManager().set_value_from_key(
                    key="project_browser_last_view", value=int(i)
                )
            )

    def force_update_json(self) -> None:
        """Download last json from gitlab pages"""
        self.plugin_manager.force_update_json_projects()
        self.lbl_date_refresh_json.setText(
            self.tr("Information last updated: ")
            + self.plugin_manager.get_date_json_projects_update
        )
        self.populate_table()

    def populate_table(self) -> None:
        """Populate the table_projects with public, gitlab, and private projects."""
        self.clear_table()
        self.add_projects_to_table(self.plugins, "plugin")
        self.add_projects_to_table(self.qgis, "qgis")
        self.add_projects_to_table(self.others, "other")

        self.table_projects.setSortingEnabled(True)

    def add_projects_to_table(self, projects: list[ProjectObj], source: str) -> None:
        """Add a list of projects to the table_projects.

        :param projects: List of ProjectObj to add.
        :param source: Source of the projects, used for the 'WhatsThis' field.
        """
        for project in projects:
            row_position = self.table_projects.rowCount()
            self.table_projects.insertRow(row_position)

            # Création de l'item pour le nom du project
            item = QTableWidgetItem(project.name)
            item.setData(Qt.ItemDataRole.UserRole, project)
            project_logo = project.logo
            project_icon_pixmap = None

            if project_logo:
                icon = self.plugin_manager.get_project_icon(project.name, project_logo)
                if icon:
                    # Set constant size for icons
                    project_icon_pixmap = icon.pixmap(icon.actualSize(QSize(100, 100)))

            if not project_icon_pixmap:
                # Empty icon if no icon
                project_icon_pixmap = QPixmap(100, 100)
                project_icon_pixmap.fill(QColor(255, 255, 255, 0))

            item.setIcon(QIcon(project_icon_pixmap.scaled(QSize(100, 100))))

            item.setWhatsThis(source)
            self.table_projects.setItem(row_position, 0, item)
            self._table_items_by_project[project.name] = item

        self.table_projects.sortItems(0, order=Qt.SortOrder.AscendingOrder)
        # Select first item
        self.table_projects.selectRow(0)

    def clear_table(self):
        """remove all line on table_projects"""
        self.table_projects.setRowCount(0)

    def display_informations(self) -> None:
        """Displays the detailed information of the selected item and configures the UI
        elements accordingly.

        Updates text fields and buttons based on the selected item's details,
        including name, version, description, and various URLs.

        :return: None
        """
        oslandia_project = self.get_selected_projet()
        if oslandia_project is not None:
            self.wdg_showcase_project.set_display_data(oslandia_project)

    def get_selected_projet(self) -> ProjectObj | None:
        """Retrieves the ProjectObj object based on the selection."""
        selected_items = self.table_projects.selectedItems()
        if selected_items:
            item = selected_items[0]
            project = item.data(Qt.ItemDataRole.UserRole)
            return project

        return None

    def _start_assets_download(self) -> None:
        """Starts downloading the icons AND screenshots that are missing
        or out of date in the background, without preventing the dialogue box from opening."""
        all_projects = self.plugins + self.qgis + self.others

        assets = self.plugin_manager.get_missing_assets(all_projects, "logo", "icons")
        assets += self.plugin_manager.get_missing_assets(
            all_projects, "screenshot", "images"
        )

        if not assets:
            return

        self.download_task = ProjectAssetsDownloadTask(assets)
        self.download_task.qsig_asset_ready.connect(self._on_asset_ready)
        self.download_task.taskCompleted.connect(self._on_download_task_finished)
        self.download_task.taskTerminated.connect(self._on_download_task_finished)
        QgsApplication.taskManager().addTask(self.download_task)

    def _on_download_task_finished(self) -> None:
        """Clears the reference to the task once it has completed (success,
        failure or cancellation)"""

        self.download_task = None

    def _on_asset_ready(self, asset: ProjectAsset) -> None:
        """Updates the UI as soon as an asset (logo or screenshot) is uploaded.

        :param asset: the downloaded asset
        :type asset: ProjectAsset
        """
        if asset.attribute_name == "logo":
            icon = QIcon(str(asset.dest_path))

            item = self._table_items_by_project.get(asset.project_name)
            if item:
                item.setIcon(icon)

            for card in self._card_widgets_by_project.get(asset.project_name, []):
                card.set_icon(icon)

        elif asset.attribute_name == "screenshot":
            selected = self.get_selected_projet()
            if selected and selected.name == asset.project_name:
                self.display_informations()

    def closeEvent(self, event) -> None:  # noqa: N802
        """Cleanly cancels the current download task upon exit."""
        if self.download_task is not None:
            try:
                if self.download_task.status() in (
                    self.download_task.TaskStatus.Queued,
                    self.download_task.TaskStatus.Running,
                ):
                    try:
                        self.download_task.qsig_asset_ready.disconnect(
                            self._on_asset_ready
                        )
                    except TypeError:
                        pass
                    self.download_task.cancel()
            except RuntimeError:
                pass
            finally:
                self.download_task = None
        super().closeEvent(event)
