from datetime import datetime
from pathlib import Path

from qgis.core import Qgis
from qgis.PyQt.QtGui import QIcon

from oslandia.constants import (
    GITLAB_PLUGINS_REMOTE,
    PRIVATE_PROJECTS_REMOTE,
    PUBLIC_PROJECTS_REMOTE,
    UTM_CONTENT_SHOWCASE,
    VALID_EXTENSIONS,
)
from oslandia.datamodels.mdl_projects_browser import ProjectsObj
from oslandia.toolbelt import PlgLogger
from oslandia.toolbelt.cache_manager import CacheManager
from oslandia.toolbelt.file_stats import is_file_older_than
from oslandia.toolbelt.network_manager import NetworkRequestsManager


class ShowcaseManager:
    def __init__(self):
        """Initialization."""
        self.cache_manager = CacheManager(".oslandia", "cache/showcase")
        self.network_manager = NetworkRequestsManager()
        self.log = PlgLogger().log

        # cache path json
        self.PUBLIC_PROJECTS_LOCAL = Path(
            self.cache_manager.get_cache_path / "public_plugins.json"
        )
        self.PRIVATE_PROJECTS_LOCAL = Path(
            self.cache_manager.get_cache_path / "private_plugins.json"
        )
        self.GITLAB_PLUGINS_LOCAL = Path(
            self.cache_manager.get_cache_path / "gitlab_plugins.json"
        )

    def update_json_projects(self):
        """Update the list of projects in the cache manager if they are more than a week old."""
        if not self.cache_manager.ensure_cache_dir_exists():
            self.cache_manager.create_cache_dir()

        self.download_if_needed(self.PUBLIC_PROJECTS_LOCAL, PUBLIC_PROJECTS_REMOTE, 30)
        self.download_if_needed(
            self.PRIVATE_PROJECTS_LOCAL, PRIVATE_PROJECTS_REMOTE, 30
        )
        self.download_if_needed(self.GITLAB_PLUGINS_LOCAL, GITLAB_PLUGINS_REMOTE, 30)

    @property
    def json_exists(self):
        """Check if json files exists."""

        if not Path(self.PUBLIC_PROJECTS_LOCAL).exists():
            return False

        if not Path(self.GITLAB_PLUGINS_LOCAL).exists():
            return False

        if not Path(self.PRIVATE_PROJECTS_LOCAL).exists():
            return False

        return True

    def force_update_json_projects(self):
        """Update the list of projects in the cache manager without regard to date."""
        if not self.cache_manager.ensure_cache_dir_exists():
            self.cache_manager.create_cache_dir()

        # Directly download without checking the file age
        self.network_manager.download_file_to(
            PUBLIC_PROJECTS_REMOTE,
            self.PUBLIC_PROJECTS_LOCAL,
            caller=UTM_CONTENT_SHOWCASE,
        )
        self.network_manager.download_file_to(
            PRIVATE_PROJECTS_REMOTE,
            self.PRIVATE_PROJECTS_LOCAL,
            caller=UTM_CONTENT_SHOWCASE,
        )
        self.network_manager.download_file_to(
            GITLAB_PLUGINS_REMOTE,
            self.GITLAB_PLUGINS_LOCAL,
            caller=UTM_CONTENT_SHOWCASE,
        )

    def download_if_needed(self, file_path: Path, url: str, days: int = 7) -> None:
        """Check if a download is required. A download is required if the file does not
        exist or is more than x days old (defined by days).

        :param file_path: Path file
        :type file_path: Path
        :param url: URL to download
        :type url: str
        :param days: File expiration time, defaults to 7
        :type days: int, optional
        """
        if is_file_older_than(
            local_file_path=file_path,
            expiration_rotating_hours=days * 24,
        ):
            self.network_manager.download_file_to(
                url, file_path, caller=UTM_CONTENT_SHOWCASE
            )
        else:
            self.log(
                message=f"File {file_path} is up to date",
                log_level=Qgis.MessageLevel.Info,
            )

    def get_last_modification_date(self, file_path: Path) -> datetime:
        """Return the last modification date of a file.

        :param file_path: Path to the file
        :type file_path: Path
        :return: The datetime of the last modification
        :rtype: datetime
        :raises FileNotFoundError: If the file does not exist
        """
        if not file_path.exists():
            raise FileNotFoundError("The specified file does not exist.")
        return datetime.fromtimestamp(file_path.stat().st_mtime)

    @property
    def get_date_json_projects_update(self) -> str:
        """Check the last modification date of a specific file.

        :param file_name: Name of the file to check
        :type file_name: str
        :return: Last modification date and time as a formatted string
        :rtype: str
        :raises FileNotFoundError: If the file does not exist
        """
        file_path = Path(self.PUBLIC_PROJECTS_LOCAL)
        last_modification_date = self.get_last_modification_date(file_path)
        return last_modification_date.strftime("%Y-%m-%d %H:%M:%S")

    def _download_project_assets_to_cache(
        self, attribute_name: str, subfolder: str
    ) -> None:
        """Download specified project assets to cache.

        :param attribute_name: Name of attribut in ProjectObj
        :type attribute_name: str
        :param subfolder: Name of subfolder witch contains images
        :type subfolder: str
        """
        projects_obj = ProjectsObj(self.cache_manager.get_cache_path)

        for project in projects_obj.get_projects():
            if attribute_name == "screenshot":
                project_subfolder = subfolder + f"/{project.name}"
            else:
                project_subfolder = subfolder

            asset_urls = getattr(project, attribute_name, None)
            if not asset_urls:
                continue

            # logo is a str, screenshot is a list: normalize to list
            if isinstance(asset_urls, str):
                asset_urls = [asset_urls]

            folder_path = Path(self.cache_manager.get_cache_path / project_subfolder)
            expected_files = []

            for index, asset_url in enumerate(asset_urls):
                extension = next(
                    (ext for ext in VALID_EXTENSIONS if ext in asset_url), None
                )
                if not extension:
                    continue
                base_name = project.name.lower().replace(" ", "_")
                if attribute_name == "screenshot":
                    filename = f"{base_name}_{index}{extension}"
                else:
                    filename = base_name + extension
                expected_files.append(filename)
                file_path = folder_path / filename
                if not file_path.exists():
                    self.download_if_needed(file_path=file_path, url=asset_url, days=30)

    def download_projects_icons_to_cache(self) -> None:
        """Download projects icons to cache."""
        self._download_project_assets_to_cache(
            attribute_name="logo",
            subfolder="icons",
        )

    def download_projects_images_to_cache(self) -> None:
        """Download projects images to cache."""
        self._download_project_assets_to_cache(
            attribute_name="screenshot",
            subfolder="images",
        )

    def get_project_icon(self, project_name: str, logo: str) -> QIcon | None:
        """Generate an icon for the project based on its logo path.

        :param project_name: Name of the project
        :type project_name: str
        :param logo: Path or name of the logo file
        :type logo: str
        :return: QIcon if logo extension is valid, otherwise None
        :rtype: Optional[QIcon]
        """
        extension = next((ext for ext in VALID_EXTENSIONS if ext in logo), None)

        if extension is None:
            return None

        icon_file = project_name.lower().replace(" ", "_") + extension
        icon_path = Path(self.cache_manager.get_cache_path / "icons" / icon_file)

        return QIcon(str(icon_path))

    def get_image_by_project_name(self, project_name: str) -> list[Path]:
        """Returns a list of screenshots and illustrations for a given project name.

        :param project_name: Project name from ProjectObj
        :type project_name: str
        :return: List of image paths
        :rtype: list
        """
        folder = Path(self.cache_manager.get_cache_path / "images" / project_name)
        if not folder.exists():
            return []
        return sorted(f for f in folder.iterdir() if f.is_file())
