############################ Copyrights and license ############################
#                                                                              #
# Copyright 2023 Enrico Minack <github@enrico.minack.dev>                      #
# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com>        #
# Copyright 2023 Jonathan Leitschuh <jonathan.leitschuh@gmail.com>             #
# Copyright 2023 Joseph Henrich <crimsonknave@gmail.com>                       #
# Copyright 2023 Trim21 <trim21.me@gmail.com>                                  #
# Copyright 2024 Enrico Minack <github@enrico.minack.dev>                      #
# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com>        #
#                                                                              #
# This file is part of PyGithub.                                               #
# http://pygithub.readthedocs.io/                                              #
#                                                                              #
# PyGithub 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.                                                           #
#                                                                              #
# PyGithub 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 PyGithub. If not, see <http://www.gnu.org/licenses/>.             #
#                                                                              #
################################################################################

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Union

from typing_extensions import TypedDict

import github.AdvisoryVulnerabilityPackage
from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet

if TYPE_CHECKING:
    from github.AdvisoryVulnerabilityPackage import AdvisoryVulnerabilityPackage


class SimpleAdvisoryVulnerabilityPackage(TypedDict):
    """
    A simple package in an advisory.
    """

    ecosystem: str
    name: str | None


class SimpleAdvisoryVulnerability(TypedDict):
    """
    A simple vulnerability in a security advisory.
    """

    package: SimpleAdvisoryVulnerabilityPackage
    patched_versions: str | None
    vulnerable_functions: list[str] | None
    vulnerable_version_range: str | None


AdvisoryVulnerabilityInput = Union[SimpleAdvisoryVulnerability, "AdvisoryVulnerability"]


class AdvisoryVulnerability(NonCompletableGithubObject):
    """
    This class represents a package that is vulnerable to a parent SecurityAdvisory.

    The reference can be found here
    https://docs.github.com/en/rest/security-advisories/repository-advisories

    """

    @property
    def package(
        self,
    ) -> AdvisoryVulnerabilityPackage:
        """
        :type: :class:`github.AdvisoryVulnerability.AdvisoryVulnerability`
        """
        return self._package.value

    @property
    def patched_versions(self) -> str:
        """
        :type: string
        """
        return self._patched_versions.value

    @property
    def vulnerable_functions(self) -> list[str] | None:
        """
        :type: list of string
        """
        return self._vulnerable_functions.value

    @property
    def vulnerable_version_range(self) -> str | None:
        """
        :type: string
        """
        return self._vulnerable_version_range.value

    def _initAttributes(self) -> None:
        self._package: Attribute[AdvisoryVulnerabilityPackage] = NotSet
        self._patched_versions: Attribute[str] = NotSet
        self._vulnerable_functions: Attribute[list[str]] = NotSet
        self._vulnerable_version_range: Attribute[str] = NotSet

    def _useAttributes(self, attributes: dict[str, Any]) -> None:
        if "package" in attributes:  # pragma no branch
            self._package = self._makeClassAttribute(
                github.AdvisoryVulnerabilityPackage.AdvisoryVulnerabilityPackage,
                attributes["package"],
            )
        if "patched_versions" in attributes:  # pragma no branch
            self._patched_versions = self._makeStringAttribute(attributes["patched_versions"])
        if "vulnerable_functions" in attributes:  # pragma no branch
            self._vulnerable_functions = self._makeListOfStringsAttribute(attributes["vulnerable_functions"])
        if "vulnerable_version_range" in attributes:  # pragma no branch
            self._vulnerable_version_range = self._makeStringAttribute(attributes["vulnerable_version_range"])

    @classmethod
    def _validate_vulnerability(cls, vulnerability: AdvisoryVulnerabilityInput) -> None:
        assert isinstance(vulnerability, (dict, cls)), vulnerability
        if isinstance(vulnerability, dict):
            assert "package" in vulnerability, vulnerability
            package: SimpleAdvisoryVulnerabilityPackage = vulnerability["package"]
            assert isinstance(package, dict), package
            assert "ecosystem" in package, package
            assert isinstance(package["ecosystem"], str), package
            assert "name" in package, package
            assert isinstance(package["name"], (str, type(None))), package
            assert "patched_versions" in vulnerability, vulnerability
            assert isinstance(vulnerability["patched_versions"], (str, type(None))), vulnerability
            assert "vulnerable_functions" in vulnerability, vulnerability
            assert isinstance(vulnerability["vulnerable_functions"], (list, type(None))), vulnerability
            assert "vulnerable_functions" in vulnerability, vulnerability
            assert (
                all(isinstance(vf, str) for vf in vulnerability["vulnerable_functions"])
                if vulnerability["vulnerable_functions"] is not None
                else True
            ), vulnerability
            assert "vulnerable_version_range" in vulnerability, vulnerability
            assert isinstance(vulnerability["vulnerable_version_range"], (str, type(None))), vulnerability

        else:
            assert (
                vulnerability.package is github.AdvisoryVulnerabilityPackage.AdvisoryVulnerabilityPackage
            ), vulnerability

    @staticmethod
    def _to_github_dict(
        vulnerability: AdvisoryVulnerabilityInput,
    ) -> SimpleAdvisoryVulnerability:
        if isinstance(vulnerability, dict):
            vulnerability_package: SimpleAdvisoryVulnerabilityPackage = vulnerability["package"]
            return {
                "package": {
                    "ecosystem": vulnerability_package["ecosystem"],
                    "name": vulnerability_package["name"],
                },
                "patched_versions": vulnerability["patched_versions"],
                "vulnerable_functions": vulnerability["vulnerable_functions"],
                "vulnerable_version_range": vulnerability["vulnerable_version_range"],
            }
        return {
            "package": {
                "ecosystem": vulnerability.package.ecosystem,
                "name": vulnerability.package.name,
            },
            "patched_versions": vulnerability.patched_versions,
            "vulnerable_functions": vulnerability.vulnerable_functions,
            "vulnerable_version_range": vulnerability.vulnerable_version_range,
        }
