"""Dialog for issue view"""

# standard
from functools import partial
from pathlib import Path
from typing import TYPE_CHECKING

# PyQGIS
from qgis.core import Qgis
from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QWidget

# project
from oslandia.__about__ import __icon_path__
from oslandia.gui.helpdesk.wdg_view_issues import ViewIssueWidget
from oslandia.gui.stylesheet import DialogStyleSheet
from oslandia.helpdesk.group import Group, GroupRequestManager
from oslandia.helpdesk.issue import Issues
from oslandia.helpdesk.project import ProjectRequestManager
from oslandia.toolbelt.log_handler import PlgLogger
from oslandia.toolbelt.preferences import PlgOptionsManager


if TYPE_CHECKING:
    from qgis.PyQt.QtWidgets import QTabWidget

    from oslandia.helpdesk.project import Project


class ViewIssueDialog(DialogStyleSheet):
    """QDialog to display issue from a selected project."""

    def __init__(self, parent: QWidget | None = None):
        """Initialize helpdesk and projects issues browser.

        :param parent: dialog parent, defaults to None
        :type parent: QWidget | None, optional
        """
        super().__init__(parent)
        uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self)

        # helpful type hints stubs
        self.tabwidget_gitlab_group: QTabWidget

        self.setWindowTitle(self.tr("Helpdesk") + " - " + self.tr("Projects"))
        self.plg_settings = PlgOptionsManager.get_plg_settings()
        self.log = PlgLogger().log

        # Don't display tab if only one group
        self.tabwidget_gitlab_group.setTabBarAutoHide(True)

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

        # Keep reference of widget for each group
        self._group_widget_dict: dict[str, ViewIssueWidget] = {}
        # Keep group for each tab index, used to update window title/icon
        self._tab_group: dict[int, Group] = {}

        manager = GroupRequestManager()

        for group_id in self.plg_settings.gitlab_group_list_values:
            group: Group | None = manager.get_group_from_id(group_id)
            if group:
                manager.download_avatar(group)
                self.add_gitlab_group(group)

        self.tabwidget_gitlab_group.currentChanged.connect(self._on_current_tab_changed)
        self._on_current_tab_changed(self.tabwidget_gitlab_group.currentIndex())

    def add_gitlab_group(self, group: Group) -> None:
        """Add tab for a gitlab group

        :param group: group to add
        :type group: Group
        """
        widget = ViewIssueWidget(group.id, self)
        tab_index: int = self.tabwidget_gitlab_group.addTab(
            widget, self._project_icon(widget), group.name
        )
        self._group_widget_dict[group.id] = widget
        self._tab_group[tab_index] = group
        widget.cbx_projects.currentIndexChanged.connect(
            partial(self._update_tab_icon, tab_index, widget)
        )
        widget.cbx_projects.currentIndexChanged.connect(
            partial(self._refresh_window_title_if_current, tab_index)
        )
        widget.qsig_project_count_changed.connect(
            partial(self._update_project_selector, tab_index, widget, group)
        )

        self._update_project_selector(
            tab_index, widget, group, widget.proxy_mdl.rowCount()
        )

    def _update_project_selector(
        self, tab_index: int, widget: ViewIssueWidget, group: Group, count: int
    ) -> None:
        """Show/hide a tab's project selector based on how many projects are
        available for selection, adjusting the tab text to match.

        Called once when the tab is created, and again whenever the widget
        reports that its visible project count changed (e.g. a maintainer
        toggling the "display archived projects" option).

        :param tab_index: tab index to update
        :type tab_index: int
        :param widget: view issue widget for that tab
        :type widget: ViewIssueWidget
        :param group: group for that tab, used to restore the tab text
        :type group: Group
        :param count: number of projects currently available for selection
        :type count: int
        """
        if count == 1:
            self.log(
                message=f"Only one project available for group {group.name}. "
                "Hiding project selector and using project's name as tab text.",
                log_level=Qgis.MessageLevel.NoLevel,
                push=False,
            )
            widget.cbx_projects.hide()
            self.tabwidget_gitlab_group.setTabText(
                tab_index, widget.cbx_projects.currentText()
            )
        else:
            widget.cbx_projects.show()
            self.tabwidget_gitlab_group.setTabText(tab_index, group.name)

    def _project_icon(self, widget: ViewIssueWidget) -> QIcon:
        """Icon of the project currently selected in a group tab's combo box.

        :param widget: view issue widget for a group tab
        :type widget: ViewIssueWidget

        :return: currently selected project's icon, empty icon if none selected
        :rtype: QIcon
        """
        index = widget.cbx_projects.currentIndex()
        if index < 0:
            return QIcon()
        return widget.cbx_projects.itemIcon(index)

    def _update_tab_icon(self, tab_index: int, widget: ViewIssueWidget) -> None:
        """Refresh a tab's icon to match its currently selected project.

        :param tab_index: tab index to update
        :type tab_index: int
        :param widget: view issue widget for that tab
        :type widget: ViewIssueWidget
        """
        self.tabwidget_gitlab_group.setTabIcon(tab_index, self._project_icon(widget))

    def _refresh_window_title_if_current(self, tab_index: int) -> None:
        """Refresh window title/icon after a project change, but only if that
        change happened in the tab currently shown to the user.

        :param tab_index: tab index whose project selection changed
        :type tab_index: int
        """
        if tab_index == self.tabwidget_gitlab_group.currentIndex():
            self._on_current_tab_changed(tab_index)

    def _window_title(self, index: int, group: Group) -> str:
        """Window title for the tab at the given index.

        With several groups configured, the tab bar is visible and the group
        name is enough context. With a single group, the tab bar auto-hides, so the
        window title is the only remaining place to show which project's issues are
        currently displayed.

        :param index: tab index
        :type index: int
        :param group: group for that tab
        :type group: Group

        :return: window title
        :rtype: str
        """
        if len(self._tab_group) == 1:
            widget = self.tabwidget_gitlab_group.widget(index)
            if isinstance(widget, ViewIssueWidget):
                project: Project | None = widget.selected_project()
                if project:
                    return f"{group.name} - {project.name}"
        return group.name

    def _on_current_tab_changed(self, index: int) -> None:
        """Update window title and icon with the group of the currently selected tab.

        :param index: index of the currently selected tab
        :type index: int
        """
        group: Group | None = self._tab_group.get(index)
        if group:
            self.setWindowTitle(self._window_title(index, group))
            self.setWindowIcon(QIcon(str(group.avatar_local_filepath)))
        else:
            self.setWindowTitle(self.tr("Helpdesk") + " / " + self.tr("Projects"))
            self.setWindowIcon(QIcon(str(__icon_path__)))

    def selected_project_id(self) -> int | None:
        """Get selected project id, return None if no project selected

        :return: selected project id
        :rtype: Optional[int]
        """
        widget = self.tabwidget_gitlab_group.currentWidget()
        if isinstance(widget, ViewIssueWidget):
            return widget.selected_project_id()
        return None

    def select_issue(self, issue: Issues) -> None:
        """Select issue in dialog

        :param issue: issue to select
        :type issue: Issues
        """
        project_manager = ProjectRequestManager()
        project = project_manager.get_project(project_id=issue.project_id)
        if project is None:
            self.log(
                message=f"Can't get project with ID {issue.project_id} for issue selection.",
                log_level=Qgis.MessageLevel.Warning,
                push=False,
            )
            return

        if project.group_id in self._group_widget_dict:
            self._group_widget_dict[project.group_id].select_issue(issue)
        else:
            self.log(
                message=f"Can't get group with ID {project.group_id} for issue selection.",
                log_level=Qgis.MessageLevel.Warning,
                push=False,
            )
