"""Widget to display a GitLab issue"""

# standard
from pathlib import Path
from typing import TYPE_CHECKING

# PyQGIS
from qgis.core import QgsApplication
from qgis.PyQt import QtCore, uic
from qgis.PyQt.QtCore import QItemSelection, QItemSelectionModel, Qt
from qgis.PyQt.QtWidgets import QAbstractItemView, QHeaderView, QMessageBox, QWidget
from qgis.utils import OverrideCursor

# project
from oslandia.constants import UTM_CONTENT_HELPDESK
from oslandia.custom_exceptions import UnavailableUserError
from oslandia.gui.helpdesk.delegate_issue_table_view import IssueTableViewDelegate
from oslandia.gui.helpdesk.dlg_create_issue import CreateIssueDialog
from oslandia.gui.helpdesk.mdl_issue import IssuesListModel
from oslandia.gui.helpdesk.mdl_project import ProjectListModel
from oslandia.gui.helpdesk.mdl_project_manager import ProjectListModelManager
from oslandia.gui.helpdesk.proxy_model_issue import IssueProxyModel
from oslandia.gui.helpdesk.proxy_model_project import ProjectProxyModel
from oslandia.gui.helpdesk.task_gitlab_notification import GitLabNotificationManager
from oslandia.gui.helpdesk.wdg_issue import IssueWidget
from oslandia.gui.stylesheet import SPLITTER, WidgetStyleSheet
from oslandia.helpdesk.group import GroupRequestManager
from oslandia.helpdesk.issue import Issues
from oslandia.helpdesk.project import Project, ProjectRequestManager
from oslandia.helpdesk.user import UserRequestManager
from oslandia.toolbelt.network_manager import open_url_in_browser
from oslandia.toolbelt.preferences import PlgOptionsManager


if TYPE_CHECKING:
    from qgis.PyQt.QtWidgets import QPushButton

    from oslandia.helpdesk.user import User


class ViewIssueWidget(WidgetStyleSheet):
    """QWidget to display issue from a selected project."""

    qsig_project_count_changed = QtCore.pyqtSignal(int)

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

        :param group_id: gitlab group id for project selection
        :type group_id: int
        :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.btn_open_helpdesk_liveview: QPushButton

        self.setWindowTitle(self.tr("Project issues"))

        # modules refs as attributes
        self.gitlab_notification_manager: GitLabNotificationManager | None = None
        self.plg_settings = PlgOptionsManager.get_plg_settings()
        self.project_request = ProjectRequestManager()

        with OverrideCursor(Qt.CursorShape.WaitCursor):
            self.group_id = group_id
            self.mdl_project = ProjectListModelManager.get_model_for_group(group_id)
            self.user_request_manager = UserRequestManager()
            self.proxy_mdl = ProjectProxyModel(self)
            self.proxy_mdl.setSourceModel(self.mdl_project)
            self.proxy_mdl.setSortCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
            self.proxy_mdl.sort(ProjectListModel.NAME_COL, Qt.SortOrder.AscendingOrder)
            self.cbx_projects.setModel(self.proxy_mdl)
            self.cbx_projects.setModelColumn(ProjectListModel.NAME_COL)

            # signal
            self.proxy_mdl.rowsInserted.connect(self._notify_project_count_changed)
            self.proxy_mdl.rowsRemoved.connect(self._notify_project_count_changed)
            self.proxy_mdl.modelReset.connect(self._notify_project_count_changed)
            self.proxy_mdl.layoutChanged.connect(self._notify_project_count_changed)

            # Display archived project option only for some users
            try:
                user: User = self.user_request_manager.get_current_user()
                display_archived = user.can_display_archived_project
                # Check if user is a maintainer for the group
                group_manager = GroupRequestManager()
                access_level = group_manager.get_user_access_level(
                    group_id=self.group_id, user_id=user.id
                )
                display_archived &= (
                    access_level is not None
                    and access_level >= self.plg_settings.min_access_level_for_archived
                )
                self.ckb_display_archived.setVisible(display_archived)
                if display_archived:
                    self.proxy_mdl.show_archived(self.ckb_display_archived.isChecked())
                else:
                    self.proxy_mdl.show_archived(False)
            except UnavailableUserError:
                self.ckb_display_archived.setVisible(False)
                self.proxy_mdl.show_archived(False)

            # Set current project  with last value used if defined in settings
            if self.plg_settings.issue_view_last_project_id:
                self.set_current_project_id(
                    self.plg_settings.issue_view_last_project_id
                )

            self.mdl_issue = IssuesListModel(self)
            self.mdl_issue.set_progress_bar(self.prg_bar_issue)
            self.mdl_issue.qsig_page_loaded_signal.connect(self._issue_page_loaded)
            self.proxy_mdl_issue = IssueProxyModel(self)
            self.proxy_mdl_issue.setSourceModel(self.mdl_issue)
            show_closed_issue = self.ckb_display_closed.isChecked()
            self.proxy_mdl_issue.show_closed(show_closed_issue)

            self.tbv_issues.setModel(self.proxy_mdl_issue)
            self.tbv_issues.setSelectionMode(
                QAbstractItemView.SelectionMode.SingleSelection
            )
            self.tbv_issues.setSelectionBehavior(
                QAbstractItemView.SelectionBehavior.SelectRows
            )
            self.tbv_issues.setSortingEnabled(True)

            self.tbv_issues.setColumnHidden(
                IssuesListModel.STATE_COL, not show_closed_issue
            )

            self.tbv_issues.selectionModel().selectionChanged.connect(
                self._selected_issue_changed
            )

            self.tbv_issues.verticalHeader().setVisible(False)
            self.tbv_issues.horizontalHeader().setSectionResizeMode(
                IssuesListModel.NAME_COL, QHeaderView.ResizeMode.Stretch
            )

            delegate = IssueTableViewDelegate(self.tbv_issues)
            self.tbv_issues.setItemDelegate(delegate)

            self.wdg_issue.set_display_mode(IssueWidget.DisplayMode.READ)
            self.wdg_issue.qsig_issue_updated.connect(self._issue_updated)

            # No issue selected by default, issue widget is invisible
            self.wdg_issue.setVisible(False)

            self.lbl_search.setToolTip(
                self.tr("Filter on issue title and description.")
            )

        # signals
        self.ckb_assign_me.clicked.connect(self._issue_filter_changed)
        self.ckb_display_archived.clicked.connect(self._project_filter_changed)
        self.ckb_display_closed.clicked.connect(self._issue_filter_changed)
        self.cbx_projects.currentIndexChanged.connect(self._project_changed)
        self.lne_search.textChanged.connect(self._issue_filter_changed)

        self.splitter.setStyleSheet(SPLITTER)

        # Simulate project changes for project_id definition in widgets
        self._project_changed()

        # buttons customization
        self.btn_refresh.setToolTip(self.tr("Refresh issues"))
        self.btn_refresh.setIcon(QgsApplication.getThemeIcon("mActionRefresh.svg"))
        self.btn_refresh.clicked.connect(self._refresh_issues)

        self.btn_issue_add.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg"))
        self.btn_issue_add.setToolTip(self.tr("New issue"))
        self.btn_issue_add.clicked.connect(self._add_issue)

        self.btn_open_helpdesk_liveview.setIcon(
            QgsApplication.getThemeIcon("mIconReport.svg")
        )
        self.btn_open_helpdesk_liveview.clicked.connect(
            self._open_helpdesk_liveview_for_current_project
        )

    def set_current_project_id(self, project_id: int) -> None:
        """Define current selected project

        :param project_id: project id
        :type project_id: int
        """
        with OverrideCursor(Qt.CursorShape.WaitCursor):
            self.mdl_project.wait_for_data_load()

            project_row = self.mdl_project.get_project_row(project_id)
            if project_row != -1:
                index = self.proxy_mdl.mapFromSource(
                    self.mdl_project.index(project_row, self.mdl_project.NAME_COL)
                )
                self.cbx_projects.setCurrentIndex(index.row())

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

        :param issue: issue to select
        :type issue: Issues
        """

        # Change selected project if needed
        if issue.project_id != self.selected_project_id():
            self.set_current_project_id(issue.project_id)

        with OverrideCursor(Qt.CursorShape.WaitCursor):
            # Force refresh of issue list
            if project_id := self.selected_project_id():
                self.mdl_issue.set_filter(project_id)

            # Wait for data load to be sure to get issue row
            self.mdl_issue.wait_for_data_load()

            issue_row = self.mdl_issue.get_issue_row(issue)
            if issue_row != -1:
                # Get proxy model index for selection
                index = self.proxy_mdl_issue.mapFromSource(
                    self.mdl_issue.index(issue_row, self.mdl_project.NAME_COL)
                )
                # Update selection model, issue will be display with signal
                self.tbv_issues.selectionModel().select(
                    index,
                    QItemSelectionModel.SelectionFlag.Select
                    | QItemSelectionModel.SelectionFlag.Rows,
                )

    def _project_filter_changed(self) -> None:
        """Update project filter model"""
        with OverrideCursor(Qt.CursorShape.WaitCursor):
            self.cbx_projects.currentIndexChanged.disconnect(self._project_changed)
            self.proxy_mdl.show_archived(self.ckb_display_archived.isChecked())
            self.cbx_projects.currentIndexChanged.connect(self._project_changed)

    def _issue_filter_changed(self) -> None:
        """Update project filter model"""
        with OverrideCursor(Qt.CursorShape.WaitCursor):
            show_closed_issue = self.ckb_display_closed.isChecked()
            self.proxy_mdl_issue.show_closed(show_closed_issue)
            user_id = (
                [self.user_request_manager.get_current_user().id]
                if self.ckb_assign_me.isChecked()
                else []
            )
            self.proxy_mdl_issue.set_visible_user(user_id)
            search_text = None
            if self.lne_search.text():
                search_text = self.lne_search.text()
            self.proxy_mdl_issue.set_search_txt(search_text)

            self.tbv_issues.setColumnHidden(
                IssuesListModel.STATE_COL, not show_closed_issue
            )

    def _notify_project_count_changed(self) -> None:
        """Re-emit the current project count whenever the proxy model's rows change."""
        self.qsig_project_count_changed.emit(self.proxy_mdl.rowCount())

    def _project_changed(self) -> None:
        """Update issue widget for current project"""
        with OverrideCursor(Qt.CursorShape.WaitCursor):
            if project_id := self.selected_project_id():
                self.mdl_issue.set_filter(project_id)

            self.btn_issue_add.setVisible(False)

            # Get project and update notification manager
            if project := self.selected_project():
                self.btn_issue_add.setVisible(not project.archived)
                if self.gitlab_notification_manager is not None:
                    self.gitlab_notification_manager.stop()

                self.gitlab_notification_manager = GitLabNotificationManager(
                    parent=self,
                    refresh_period_in_seconds=self.plg_settings.notification_frequency,
                    projects=[project],
                    message_bar=self.msgbar_notification,
                    issue_clicked_callable=self._issue_notification_clicked,
                )
                self.gitlab_notification_manager.start()

                # display metadata
                self.lbl_project_description.setText(
                    project.description
                    if project.description
                    else self.tr("No description")
                )

                # liveview opener button
                if project.group_type in ("helpdesk", "maintenance"):
                    self.btn_open_helpdesk_liveview.setEnabled(True)
                    self.btn_open_helpdesk_liveview.setToolTip(
                        self.tr(
                            "Open live view for project: {}".format(project.name)
                            if project
                            else self.tr("No project selected")
                        )
                    )
                else:
                    self.btn_open_helpdesk_liveview.setEnabled(False)
                    self.btn_open_helpdesk_liveview.setToolTip(
                        self.tr(
                            "Liveview unavailable for this project type: {}".format(
                                project.group_type
                            )
                        )
                    )

            # Table selection is cleared, don't display issue widget
            self.wdg_issue.setVisible(False)

    def _open_helpdesk_liveview_for_current_project(self) -> None:
        """Open helpdesk liveview for the currently selected project."""
        project: Project | None = self.selected_project()
        if (
            project
            and project.group_type in ("helpdesk", "maintenance")
            and project.helpdesk_api_liveview_url
        ):
            open_url_in_browser(
                project.helpdesk_api_liveview_url,
                UTM_CONTENT_HELPDESK,
            )

    def _issue_notification_clicked(self, issue: Issues) -> None:
        """Select issue when notification is clicked

        :param issue: issue clicked
        :type issue: Issues
        """
        self.select_issue(issue)

    def _issue_updated(self, issue: Issues) -> None:
        """Update current selected row issue

        :param issue: updated issue
        :type issue: Issues
        """
        selected_index = self.tbv_issues.selectionModel().selectedRows()
        if selected_index:
            index = self.proxy_mdl_issue.mapToSource(selected_index[0])
            self.mdl_issue.set_row_object(index.row(), issue)

    def _refresh_issues(self) -> None:
        """Refresh visible issues"""
        # Simulate a project changes
        self._project_changed()

    def _add_issue(self) -> None:
        """Add issue for selected project"""
        project_id = self.selected_project_id()
        dlg_create_issue = CreateIssueDialog(self.group_id, self)
        dlg_create_issue.set_current_project_id(project_id)
        dlg_create_issue.setModal(False)
        if dlg_create_issue.exec():
            self._refresh_issues()

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

        :return: selected project id
        :rtype: Optional[int]
        """
        project_index = self.cbx_projects.currentIndex()
        if project_index != -1:
            return self.mdl_project.data(
                self.proxy_mdl.mapToSource(
                    self.proxy_mdl.index(project_index, ProjectListModel.ID)
                )
            )
        return None

    def selected_project(self) -> Project | None:
        """Get selected project, return None if no project selected

        :return: selected project
        :rtype: Optional[Project]
        """
        project_index = self.cbx_projects.currentIndex()
        if project_index != -1:
            return self.mdl_project.data(
                self.proxy_mdl.mapToSource(
                    self.proxy_mdl.index(project_index, ProjectListModel.NAME_COL)
                ),
                Qt.ItemDataRole.UserRole,
            )
        return None

    def _selected_issue_changed(
        self, selected: QItemSelection, deselected: QItemSelection
    ) -> None:
        """Update visible issue"""

        # Check if current issue is updated
        if (
            self.wdg_issue.get_display_mode() == IssueWidget.DisplayMode.UPDATE
            and self.wdg_issue.is_issue_changed()
        ):
            msg_box = QMessageBox(self)
            msg_box.setIcon(QMessageBox.Icon.Information)
            msg_box.setText(
                self.tr(
                    "Updated issue won't be saved. Do you want to change displayed issue ?"
                )
            )
            msg_box.setWindowTitle(self.tr("Changes unsaved"))
            msg_box.setStandardButtons(
                QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel
            )

            # Restore previous selection
            if msg_box.exec() != QMessageBox.StandardButton.Ok:
                self.tbv_issues.selectionModel().selectionChanged.disconnect(
                    self._selected_issue_changed
                )
                self.tbv_issues.selectionModel().select(
                    deselected, QItemSelectionModel.SelectionFlag.ClearAndSelect
                )
                self.tbv_issues.selectionModel().selectionChanged.connect(
                    self._selected_issue_changed
                )
                return

        with OverrideCursor(Qt.CursorShape.WaitCursor):
            selected_index = self.tbv_issues.selectionModel().selectedRows()
            if selected_index:
                issue: Issues = self.mdl_issue.data(
                    self.proxy_mdl_issue.mapToSource(
                        self.proxy_mdl_issue.index(
                            selected_index[0].row(), IssuesListModel.NAME_COL
                        )
                    ),
                    Qt.ItemDataRole.UserRole,
                )
                if issue:
                    # Need to define selected project full path for GraphQL use
                    if project := self.selected_project():
                        issue.project_full_path = project.full_path
                    self.wdg_issue.set_issue(issue)
                    self.wdg_issue.set_display_mode(IssueWidget.DisplayMode.READ)
                    self.wdg_issue.setVisible(True)

    def _issue_page_loaded(self, page: int) -> None:
        """Slot for issue table page load

        :param page: index of loaded page
        :type page: int
        """
        # Resize content for issue date
        self.tbv_issues.horizontalHeader().setSectionResizeMode(
            self.mdl_issue.COMMENTS_COL, QHeaderView.ResizeMode.ResizeToContents
        )
        self.tbv_issues.horizontalHeader().setSectionResizeMode(
            self.mdl_issue.CREATED_DATE_COL, QHeaderView.ResizeMode.ResizeToContents
        )
        self.tbv_issues.horizontalHeader().setSectionResizeMode(
            self.mdl_issue.TOTAL_TIME_SPENT, QHeaderView.ResizeMode.ResizeToContents
        )
        self.tbv_issues.horizontalHeader().setSectionResizeMode(
            self.mdl_issue.UPDATED_DATE_COL, QHeaderView.ResizeMode.ResizeToContents
        )
        # TODO : could could add more information in page_loaded_signal signal from AbstractGitlabObjectModel
        # This could be used to display info about load progress
