############################ Copyrights and license ############################
#                                                                              #
# Copyright 2023 Jonathan Leitschuh <Jonathan.Leitschuh@gmail.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/>.             #
#                                                                              #
################################################################################

import sys
import typing
from typing import List

import github.GithubObject
import github.RepositoryAdvisoryVulnerabilityPackage

if sys.version_info >= (3, 8):
    # TypedDict is only available in Python 3.8 and later
    class SimpleAdvisoryVulnerabilityPackage(typing.TypedDict):
        """
        A simple package in an advisory.
        """

        ecosystem: str
        name: typing.Optional[str]  # TODO: Python 3.11 make 'NotRequired'

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

        package: SimpleAdvisoryVulnerabilityPackage
        patched_versions: typing.Optional[str]  # TODO: Python 3.11 make 'NotRequired'
        vulnerable_functions: typing.Optional[
            List[str]
        ]  # TODO: Python 3.11 make 'NotRequired'
        vulnerable_version_range: typing.Optional[
            str
        ]  # TODO: Python 3.11 make 'NotRequired'

else:
    SimpleAdvisoryVulnerabilityPackage = typing.Dict[str, typing.Any]
    SimpleAdvisoryVulnerability = typing.Dict[str, typing.Any]

AdvisoryVulnerability = typing.Union[
    SimpleAdvisoryVulnerability, "RepositoryAdvisoryVulnerability"
]


class RepositoryAdvisoryVulnerability(github.GithubObject.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,
    ) -> github.RepositoryAdvisoryVulnerabilityPackage.RepositoryAdvisoryVulnerabilityPackage:
        """
        :type: :class:`github.RepositoryAdvisoryVulnerability.RepositoryAdvisoryVulnerability`
        """
        return self._package.value

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

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

    @property
    def vulnerable_version_range(self) -> typing.Optional[str]:
        """
        :type: string
        """
        return self._vulnerable_version_range.value

    # noinspection PyPep8Naming
    def _initAttributes(self):
        self._package = github.GithubObject.NotSet
        self._patched_versions = github.GithubObject.NotSet
        self._vulnerable_functions = github.GithubObject.NotSet
        self._vulnerable_version_range = github.GithubObject.NotSet

    # noinspection PyPep8Naming
    def _useAttributes(self, attributes):
        if "package" in attributes:  # pragma no branch
            self._package = self._makeClassAttribute(
                github.RepositoryAdvisoryVulnerabilityPackage.RepositoryAdvisoryVulnerabilityPackage,
                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: AdvisoryVulnerability) -> 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.RepositoryAdvisoryVulnerabilityPackage.RepositoryAdvisoryVulnerabilityPackage
            ), vulnerability

    @staticmethod
    def _to_github_dict(
        vulnerability: AdvisoryVulnerability,
    ) -> 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,
        }
