from abc import abstractmethod
from typing import Any

from qgis.core import QgsTask
from qgis.PyQt.QtCore import QObject, Qt
from qgis.PyQt.QtGui import QColor, QIcon

from oslandia.gui.helpdesk.mdl_gitlab_object import AbstractGitlabObjectModel
from oslandia.helpdesk.gitlab_object import PageLoadResult
from oslandia.helpdesk.project import Project, ProjectRequestManager
from oslandia.toolbelt.qt_utils import to_qdatetime


class ProjectListModel(AbstractGitlabObjectModel):
    ID = 0
    DATE_COL = 1
    NAME_COL = 2
    ARCHIVED_COL = 3

    def __init__(self, parent: QObject | None = None):
        """
        QStandardItemModel for datastore list display

        Args:
            parent: QObject parent
        """
        super().__init__(parent)

    @abstractmethod
    def _column_headers(self) -> list[str]:
        """Returns headers list"""
        return [
            self.tr("Id"),
            self.tr("Date"),
            self.tr("Name"),
            self.tr("Archived"),
        ]

    @abstractmethod
    def _object_data_for_role(
        self,
        gitlab_object: Any,
        col: int,
        role: Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole,
    ) -> Any:
        """Return qt model data for a specific column and role

        :param gitlab_object: object to display
        :type gitlab_object: Any
        :param col: column for table display
        :type col: int
        :param role: qt role, defaults to Qt.ItemDataRole.DisplayRole
        :type role: ItemDataRole., optional
        :return: qt model data
        :rtype: Any
        """
        project: Project = gitlab_object

        if role == Qt.ItemDataRole.DisplayRole:
            res = {
                self.ID: project.id,
                self.NAME_COL: self._display_name(project),
                self.DATE_COL: to_qdatetime(project.created_at),
                self.ARCHIVED_COL: project.archived,
            }
            return res.get(col, None)
        if role == Qt.ItemDataRole.UserRole:
            res = {self.NAME_COL: project}
            return res.get(col, None)

        if role == Qt.ItemDataRole.DecorationRole:
            res = {self.NAME_COL: QIcon(f"{project.avatar_local_filepath.resolve()}")}
            return res.get(col, None)

        if role == Qt.ItemDataRole.ForegroundRole and project.archived:
            return QColor("grey")

        return None

    def _display_name(self, project: Project) -> str:
        """Build the display name for a project using its name and its subgroup if any.
        If the project is archived, the display name is suffixed with an archived marker.

        :param project: project to build a display name for
        :type project: Project

        :return: display name, optionally prefixed with the subgroup name
            and/or suffixed with an archived marker
        :rtype: str
        """
        prj_name_to_display: str = project.name
        if project.group_id != self.obj_filter:
            prj_name_to_display = f"{project.group} / {prj_name_to_display}"
        if project.archived:
            prj_name_to_display = self.tr("{} - archived").format(prj_name_to_display)
        return prj_name_to_display

    def _load_task_description(self) -> str:
        """Test display in QGIS task manager for page load task

        :return: page load description
        :rtype: str
        """
        return self.tr("Load project for group {}").format(self.obj_filter)

    @abstractmethod
    def _load_object_page(
        self, task: QgsTask | None, obj_filter: Any, page: int
    ) -> PageLoadResult | None:
        """Load project list for a group for a specific page

        :param task: task used for load, can be None
        :type task: QgsTask, optional
        :param obj_filter: filter reprensenting group id. Must be a str
        :type obj_filter: Any
        :param page: page to load
        :type page: int
        :return: page load result, None in case of error
        :rtype: Optional[PageLoadResult]
        """
        try:
            manager = ProjectRequestManager()
            result = manager.get_project_list_by_page(
                obj_filter, page=page, limit=self.nb_item_by_page
            )
            for project in result.gitlab_object_list:
                manager.download_avatar(project)
            return result
        except ConnectionError:
            return None

    @abstractmethod
    def _loading_col(self) -> int:
        """Load issue list for a project for a specific page

        :param task: task used for load, can be None
        :type task: QgsTask, optional
        :param project_id: project id
        :type project_id: int
        :param page: page to load
        :type page: int
        :return: dict with load result
        :rtype: Dict[str, Union[int, list[object]]]
        """
        return self.NAME_COL

    def get_project_row(self, project_id: int) -> int:
        """Return model row for a project id. -1 if project is not available

        :param project_id: project unique identifier
        :type project_id: int

        :return: row model matching project ID
        :rtype: int
        """
        for row in range(self.rowCount()):
            if self.data(self.index(row, self.ID)) == project_id:
                return row
        return -1
