# standard
import json
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING

# PyQGIS
from qgis.core import Qgis
from qgis.PyQt.QtCore import QUrl

# plugin
from oslandia.constants import GITLAB_GROUP_TYPES, UTM_CONTENT_ISSUEBROWSER
from oslandia.helpdesk.gitlab_object import (
    PageLoadResult,
    get_nb_result_and_page_from_reply,
)
from oslandia.toolbelt import PlgLogger, PlgOptionsManager
from oslandia.toolbelt.network_manager import NetworkRequestsManager


if TYPE_CHECKING:
    from oslandia.toolbelt.preferences import PlgSettingsStructure


@dataclass
class Project:
    id: int
    name: str
    group: str
    group_id: int
    archived: bool
    created_at: datetime
    last_activity_at: datetime
    description: str | None = None
    avatar_url: str | None = None
    path: str | None = None
    full_path: str = ""  # full path used for GraphQL use
    # defined during post init
    api_url: str = field(init=False)
    avatar_local_filepath: Path = field(init=False)
    group_type: GITLAB_GROUP_TYPES = field(init=False)
    helpdesk_api_liveview_url: str | None = field(init=False, default=None)

    def __post_init__(self) -> None:
        """Post init method to define some of properties of the class."""
        plg_settings: PlgSettingsStructure = PlgOptionsManager.get_plg_settings()
        local_avatars_cache_folder: Path = plg_settings.local_app_folder.joinpath(
            "avatars"
        )
        self.avatar_local_filepath = local_avatars_cache_folder.joinpath(
            f"prj_{self.id}.png"
        )
        self.api_url = f"{plg_settings.gitlab_api_url_base}projects/{self.id}/"

        # group type
        self.group_type = GITLAB_GROUP_TYPES.get(self.group_id)
        if self.group_type in ("helpdesk", "maintenance"):
            self.helpdesk_api_liveview_url = (
                f"{plg_settings.helpdesk_api_url_base}support/{self.path}"
            )


class ProjectRequestManager:
    """Manager for handling project requests."""

    MAX_LIMIT: int = 50

    def __init__(self):
        """Helper for stored_data request."""
        self.log = PlgLogger().log
        self.ntwk_requester_blk = NetworkRequestsManager()
        self.plg_settings = PlgOptionsManager.get_plg_settings()

    def get_base_url(self, group_id: int) -> str:
        """Get base url for listing projects of a group.

        :param group_id: group unique identifier
        :type group_id: int

        :return: URL for group's projects informations
        :rtype: str
        """
        return f"{self.plg_settings.gitlab_api_url_base}groups/{group_id}/projects"

    def get_project(self, project_id: int) -> Project | None:
        """Get a project if available.

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

        :return: project if available
        :rtype: Optional[Project]
        """
        self.log(f"{__name__}.get_project(project_id:{project_id})")
        # send request
        try:
            req_reply = self.ntwk_requester_blk.get_url(
                QUrl(f"{self.plg_settings.gitlab_api_url_base}projects/{project_id}"),
                self.plg_settings.authentification_config_id,
                return_req_reply=False,
                caller=UTM_CONTENT_ISSUEBROWSER,
            )
            if req_reply:
                data = json.loads(req_reply.data().decode("utf-8"))
                return Project(
                    id=data["id"],
                    name=data["name"],
                    group=data["namespace"]["name"],
                    group_id=data["namespace"]["id"],
                    archived=data["archived"],
                    created_at=datetime.fromisoformat(data["created_at"].strip()),
                    last_activity_at=datetime.fromisoformat(
                        data["last_activity_at"].strip()
                    ),
                    description=data["description"],
                    avatar_url=data.get("avatar_url"),
                    path=data.get("path"),
                    full_path=data.get("path_with_namespace"),
                )
            else:
                return None
        except ConnectionError:
            return None

    def get_project_list(self, group_id: int) -> list[Project]:
        """Get list of group's project.

        :param group_id: group unique identifier
        :type group_id: int

        :return: list of available project
        :rtype: list[Project]
        """
        self.log(f"{__name__}.get_project_list(group:{group_id})")
        result = []
        page_result = self.get_project_list_by_page(group_id, 1, self.MAX_LIMIT)
        nb_page = page_result.nb_page
        result += page_result.gitlab_object_list
        for page in range(2, nb_page + 1):
            page_result = self.get_project_list_by_page(group_id, page, self.MAX_LIMIT)
            result += page_result.gitlab_object_list

        return result

    def get_project_list_by_page(
        self, group_id: int, page: int = 1, limit: int = MAX_LIMIT
    ) -> PageLoadResult:
        """Get list of projects for a group.

        :param group_id: group unique identifier
        :type group_id: int
        :param page: page number, defaults to 1
        :type page: int, optional
        :param limit: number of results per page, defaults to MAX_LIMIT
        :type limit: int, optional

        :return: list of available project
        :rtype: list[Project]
        """

        # send request
        req_reply = self.ntwk_requester_blk.get_url(
            QUrl(
                f"{self.get_base_url(group_id)}?"
                f"page={page}&per_page={limit}&include_subgroups=true"
            ),
            self.plg_settings.authentification_config_id,
            return_req_reply=True,
            caller=UTM_CONTENT_ISSUEBROWSER,
        )
        if req_reply:
            nb_result, nb_page = get_nb_result_and_page_from_reply(req_reply)
            data = json.loads(req_reply.content().data().decode("utf-8"))

            return PageLoadResult(
                nb_object=nb_result,
                nb_page=nb_page,
                page=page,
                gitlab_object_list=[
                    Project(
                        id=p_data["id"],
                        name=p_data["name"],
                        group=p_data["namespace"]["name"],
                        group_id=p_data["namespace"]["id"],
                        archived=p_data["archived"],
                        created_at=datetime.fromisoformat(p_data["created_at"].strip()),
                        last_activity_at=datetime.fromisoformat(
                            p_data["last_activity_at"].strip()
                        ),
                        avatar_url=p_data.get("avatar_url"),
                        description=p_data.get("description"),
                        path=p_data.get("path"),
                        full_path=p_data.get("path_with_namespace"),
                    )
                    for p_data in data
                ],
            )

        return PageLoadResult(nb_object=0, nb_page=0, page=0, gitlab_object_list=[])

    def download_avatar(self, project: Project) -> Path | None:
        """Download a project's avatar if it does not exist in the local cache folder.
        File is stored in local cache.

        :param project: project to download avatar for
        :type project: Project

        :return: local path to the downloaded avatar
        :rtype: Optional[Path]
        """
        if (
            not project.avatar_local_filepath.exists()
            and project.avatar_url is not None
        ):
            self.log(
                f"Downloading avatar for {project.name}",
                log_level=Qgis.MessageLevel.Info,
                push=False,
            )
            self.ntwk_requester_blk.download_file_to(
                remote_url=QUrl(f"{project.api_url}avatar"),
                local_path=project.avatar_local_filepath,
                auth_cfg=self.plg_settings.authentification_config_id,
                caller=UTM_CONTENT_ISSUEBROWSER,
            )
        return project.avatar_local_filepath
