#
# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import annotations

from logging import getLogger
from typing import TYPE_CHECKING
from urllib.parse import urljoin

from requests import codes

from gitlabracadabra.packages.package_file import PackageFile
from gitlabracadabra.packages.source import Source

if TYPE_CHECKING:
    from requests.models import Response

    from gitlabracadabra.packages.destination import Destination

logger = getLogger(__name__)


class PulpManifestSource(Source):
    """PULP_MANIFEST repository."""

    def __init__(
        self,
        *,
        log_prefix: str = "",
        url: str,
        package_name: str,
        package_version: str | None = None,
    ) -> None:
        """Initialize a PULP_MANIFEST Source object.

        Args:
            log_prefix: Log prefix.
            url: PULP_MANIFEST URL.
            package_name: Destination package name.
            package_version: Destination package version.
        """
        super().__init__()
        self._log_prefix = log_prefix
        self._url = url
        self._package_name = package_name
        self._package_version = package_version or "0"

    def __str__(self) -> str:
        """Return string representation.

        Returns:
            A string.
        """
        return f"PULP_MANIFEST repository (url={self._url})"

    def package_files(
        self,
        destination: Destination,
    ) -> list[PackageFile]:
        """Return list of package files.

        Returns:
            List of package files.
        """
        pulp_manifest = self._package_file(None)
        pulp_manifest.force = True
        destination_pulp_manifest_url = destination.get_url(pulp_manifest)
        destination_pulp_manifest_response = destination.session.request(
            "GET", destination_pulp_manifest_url, stream=True
        )
        current_pulp_manifest_files: list[PackageFile] = []
        if destination_pulp_manifest_response.status_code == codes["ok"]:
            current_pulp_manifest_files = self._pulp_manifest_files(destination_pulp_manifest_response)
        elif destination_pulp_manifest_response.status_code != codes["not_found"]:
            logger.warning(
                '%sUnexpected HTTP status for %s package file "%s" from "%s" version %s (%s): received %i %s with GET method on destination',
                self._log_prefix,
                pulp_manifest.package_type,
                pulp_manifest.file_name,
                pulp_manifest.package_name,
                pulp_manifest.package_version,
                destination_pulp_manifest_url,
                destination_pulp_manifest_response.status_code,
                destination_pulp_manifest_response.reason,
            )
            return []

        source_pulp_manifest_response = self.session.request("GET", self._url, stream=True)
        if source_pulp_manifest_response.status_code != codes["ok"]:
            logger.warning(
                "%sUnexpected HTTP status for PULP_MANIFEST url %s: received %i %s",
                self._log_prefix,
                self._url,
                source_pulp_manifest_response.status_code,
                source_pulp_manifest_response.reason,
            )
            return []
        target_pulp_manifest_files = self._pulp_manifest_files(source_pulp_manifest_response)
        package_files: list[PackageFile] = []
        for package_file in target_pulp_manifest_files:
            if package_file not in current_pulp_manifest_files:
                package_file.force = True
                package_files.append(package_file)
        package_files.append(pulp_manifest)
        for package_file in current_pulp_manifest_files:
            if package_file not in target_pulp_manifest_files:
                package_file.delete = True
                package_file.force = True
                package_files.append(package_file)
        if len(package_files) > 1:
            old_pulp_manifest = self._package_file(None)
            old_pulp_manifest.delete = True
            old_pulp_manifest.force = True
            package_files.append(old_pulp_manifest)
            destination.cache_project_package_package_files("generic", self._package_name, self._package_version)
        else:
            package_files.remove(pulp_manifest)
        return package_files

    def _pulp_manifest_files(self, pulp_manifest_response: Response) -> list[PackageFile]:
        if pulp_manifest_response.encoding is None:
            pulp_manifest_response.encoding = "utf-8"
        package_files: list[PackageFile] = []
        for pulp_manifest_line in pulp_manifest_response.iter_lines(decode_unicode=True):
            try:
                filename, sha256_checksum, size_in_bytes = pulp_manifest_line.split(",", 3)
            except ValueError:
                logger.warning(
                    "%sInvalid PULP_MANIFEST line: %s",
                    self._log_prefix,
                    pulp_manifest_line,
                )
                continue
            package_file = self._package_file(filename, sha256_checksum, size_in_bytes)
            if package_file:
                package_files.append(package_file)
        return package_files

    def _package_file(
        self, filename: str | None, sha256_checksum: str | None = None, size_in_bytes: str | None = None
    ) -> PackageFile:
        url = urljoin(self._url, filename, allow_fragments=False)
        package_file = PackageFile(
            url,
            "generic",
            self._package_name,
            self._package_version,
            url.split("/").pop(),
        )
        if sha256_checksum is not None:
            package_file.metadata["sha256_checksum"] = sha256_checksum
        if size_in_bytes is not None:
            package_file.metadata["size_in_bytes"] = size_in_bytes
        return package_file
