"""Defines the common interface for a vulnerability scanner."""
# This file is part of hoppr-cop
#
# Licensed under the MIT License;
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Copyright (c) 2022 Lockheed Martin Corporation
from __future__ import annotations

import distutils.spawn
import os

from typing import TYPE_CHECKING, ClassVar

import typer

from packageurl import PackageURL


if TYPE_CHECKING:
    from hoppr_cyclonedx_models.cyclonedx_1_3 import (
        CyclonedxSoftwareBillOfMaterialSpecification as Bom_1_3,
    )
    from hoppr_cyclonedx_models.cyclonedx_1_4 import (
        CyclonedxSoftwareBillOfMaterialsStandard as Bom_1_4,
        Vulnerability,
    )


class VulnerabilityMeta(type):
    """a meta class to define interface expectations of a vulnerability scanner."""

    @classmethod
    def __instancecheck__(cls, instance: object) -> bool:
        return cls.__subclasscheck__(type(instance))

    @classmethod
    def __subclasscheck__(cls, subclass: type) -> bool:
        return (
            hasattr(subclass, "get_vulnerabilities_by_purl")
            and callable(subclass.get_vulnerabilities_by_purl)
            and hasattr(subclass, "get_vulnerabilities_by_sbom")
            and callable(subclass.get_vulnerabilities_by_sbom)
            and hasattr(subclass, "should_activate")
            and callable(subclass.should_activate)
        )


class VulnerabilitySuper(metaclass=VulnerabilityMeta):
    """A super class that defines the expected interface for a vulnerability scanner."""

    required_tools_on_path: ClassVar[list[str]] = []
    required_environment_variables: ClassVar[list[str]] = []
    offline_mode_supported = False
    offline_mode = False

    def get_vulnerabilities_by_purl(self, purls: list[PackageURL]) -> dict[str, list[Vulnerability] | None]:
        """Get the vulnerabilities for a list of package URLS (purls).

        Return a dictionary of package URL to vulnerabilities or none if no vulnerabilities are found.
        """
        return {}

    def get_vulnerabilities_by_sbom(self, bom: Bom_1_4 | Bom_1_3) -> dict[str, list[Vulnerability] | None]:
        """Accepts a cyclone dx compatible BOM and returns a list of vulnerabilities.

        Return a dictionary of package URL to vulnerabilities or none if no vulnerabilities are found.
        """
        purls = [
            PackageURL.from_string(component.purl)
            for component in bom.components or []
            if component.purl is not None and component.purl != ""
        ]

        return self.get_vulnerabilities_by_purl(purls)

    def should_activate(self) -> bool:
        """Checks if the vulnerability scanner should activate based on it's requirements."""
        activate = True
        for tool in self.required_tools_on_path:
            if not distutils.spawn.find_executable(tool):
                activate = False
                typer.echo(f"{type(self).__name__} is not activated because {tool} is not on the PATH")
        for env in self.required_environment_variables:
            if env not in os.environ:
                activate = False
                typer.echo(
                    f"{type(self).__name__} is not activated because required environment variable {env} is not set",
                )

        if self.offline_mode and not self.offline_mode_supported:
            activate = False
            typer.echo(f"{type(self).__name__} is not activated because offline mode is not supported by this scanner")

        return activate
