import hashlib
import json
from enum import Enum, StrEnum
from typing import cast

from pydantic import BaseModel

from labels.model.advisories import Advisory
from labels.model.constraints import NonEmptyStr, TrimmedNonEmptyStr
from labels.model.ecosystem_data.aliases import AcceptedEcosystemData
from labels.model.file import Location
from labels.model.metadata import HealthMetadata


class Platform(StrEnum):
    ALPM = "ALPM"
    APK = "ALPINE"
    BINARY = "BINARY"
    CABAL = "CABAL"
    COMPOSER = "COMPOSER"
    CRAN = "CRAN"
    DEBIAN = "DEBIAN"
    GEM = "GEM"
    GO = "GO"
    GRAALVM_NATIVE_IMAGE = "GRAALVM_NATIVE_IMAGE"
    JENKINS_PLUGIN = "JENKINS_PLUGIN"
    LINUX_KERNEL = "LINUX_KERNEL"
    LINUX_KERNEL_MODULE = "LINUX_KERNEL_MODULE"
    MAVEN = "MAVEN"
    MSRC_KB = "MSRC_KB"
    NIXPKGS = "NIXPKGS"
    NPM = "NPM"
    NUGET = "NUGET"
    PIP = "PIP"
    PORTAGE = "PORTAGE"
    PUB = "PUB"
    RPM = "RPM"
    SWIFT = "SWIFT"
    UNKNOWN = "UNKNOWN"
    WORDPRESS_PLUGIN = "WORDPRESS_PLUGIN"


class Ecosystem(StrEnum):
    ALPM = "alpm"
    ALPINE = "alpine"
    BINARY = "binary"
    CABAL = "cabal"
    CRAN = "cran"
    DEBIAN = "debian"
    GO = "go"
    GRAALVM_NATIVE_IMAGE = "graalvm_native_image"
    JENKINS_PLUGIN = "jenkins_plugin"
    LINUX_KERNEL = "linux_kernel"
    LINUX_KERNEL_MODULE = "linux_kernel_module"
    MAVEN = "maven"
    MSRC_KB = "msrc_kb"
    NIXPKGS = "nixpkgs"
    NPM = "npm"
    NUGET = "nuget"
    PACKAGIST = "packagist"
    PORTAGE = "portage"
    PUB = "pub"
    PYPI = "pypi"
    RPM = "rpm"
    RUBYGEMS = "rubygems"
    SWIFTURL = "swifturl"
    WORDPRESS_PLUGIN = "wordpress_plugin"


class Language(StrEnum):
    UNKNOWN_LANGUAGE = "unknown_language"
    DART = "dart"
    DOTNET = "dotnet"
    GO = "go"
    HASKELL = "haskell"
    JAVA = "java"
    JAVASCRIPT = "javascript"
    PHP = "php"
    PYTHON = "python"
    R = "R"
    RUBY = "ruby"
    SWIFT = "swift"


class PackageType(Enum):
    UnknownPkg = "UnknownPackage"
    AlpmPkg = "alpm"
    ApkPkg = "apk"
    BinaryPkg = "binary"
    CocoapodsPkg = "pod"
    DartPubPkg = "dart-pub"
    DebPkg = "deb"
    DotnetPkg = "dotnet"
    GemPkg = "gem"
    GemspecPkg = "gemspec"
    GoModulePkg = "go-module"
    GraalVMNativeImagePkg = "graalvm-native-image"
    HackagePkg = "hackage"
    JavaPkg = "java-archive"
    JenkinsPluginPkg = "jenkins-plugin"
    KbPkg = "msrc-kb"
    LinuxKernelPkg = "linux-kernel"
    LinuxKernelModulePkg = "linux-kernel-module"
    NixPkg = "nix"
    NpmPkg = "npm"
    PhpComposerPkg = "php-composer"
    PhpPeclPkg = "php-pecl-pkg"
    PhpPearPkg = "php-pear"
    PortagePkg = "portage"
    PythonPkg = "python"
    Rpkg = "R-package"
    RpmPkg = "rpm"
    SwiftPkg = "swift"
    WordpressPluginPkg = "wordpress-plugin"

    def get_platform_value(self) -> str | None:
        package_type_to_platform = {
            PackageType.CocoapodsPkg: Platform.SWIFT.value,
            PackageType.DartPubPkg: Platform.PUB.value,
            PackageType.DotnetPkg: Platform.NUGET.value,
            PackageType.GemPkg: Platform.GEM.value,
            PackageType.GemspecPkg: Platform.GEM.value,
            PackageType.GoModulePkg: Platform.GO.value,
            PackageType.HackagePkg: Platform.CABAL.value,
            PackageType.JavaPkg: Platform.MAVEN.value,
            PackageType.NpmPkg: Platform.NPM.value,
            PackageType.PhpComposerPkg: Platform.COMPOSER.value,
            PackageType.PhpPeclPkg: Platform.COMPOSER.value,
            PackageType.PythonPkg: Platform.PIP.value,
            PackageType.Rpkg: Platform.CRAN.value,
            PackageType.SwiftPkg: Platform.SWIFT.value,
            PackageType.AlpmPkg: Platform.ALPM.value,
            PackageType.ApkPkg: Platform.APK.value,
            PackageType.BinaryPkg: Platform.BINARY.value,
            PackageType.DebPkg: Platform.DEBIAN.value,
            PackageType.GraalVMNativeImagePkg: Platform.GRAALVM_NATIVE_IMAGE.value,
            PackageType.JenkinsPluginPkg: Platform.JENKINS_PLUGIN.value,
            PackageType.KbPkg: Platform.MSRC_KB.value,
            PackageType.LinuxKernelPkg: Platform.LINUX_KERNEL.value,
            PackageType.LinuxKernelModulePkg: Platform.LINUX_KERNEL_MODULE.value,
            PackageType.NixPkg: Platform.NIXPKGS.value,
            PackageType.PortagePkg: Platform.PORTAGE.value,
            PackageType.RpmPkg: Platform.RPM.value,
            PackageType.WordpressPluginPkg: Platform.WORDPRESS_PLUGIN.value,
            PackageType.UnknownPkg: None,
        }
        return package_type_to_platform.get(self, Platform.UNKNOWN.value)

    def get_ecosystem_value(self) -> str | None:
        package_type_to_ecosystem: dict[PackageType, str | None] = {
            PackageType.AlpmPkg: Ecosystem.ALPM,
            PackageType.ApkPkg: Ecosystem.ALPINE,
            PackageType.BinaryPkg: Ecosystem.BINARY,
            PackageType.CocoapodsPkg: Ecosystem.SWIFTURL,
            PackageType.DartPubPkg: Ecosystem.PUB,
            PackageType.DebPkg: Ecosystem.DEBIAN,
            PackageType.DotnetPkg: Ecosystem.NUGET,
            PackageType.GemPkg: Ecosystem.RUBYGEMS,
            PackageType.GemspecPkg: Ecosystem.RUBYGEMS,
            PackageType.GoModulePkg: Ecosystem.GO,
            PackageType.GraalVMNativeImagePkg: Ecosystem.GRAALVM_NATIVE_IMAGE,
            PackageType.HackagePkg: Ecosystem.CABAL,
            PackageType.JavaPkg: Ecosystem.MAVEN,
            PackageType.JenkinsPluginPkg: Ecosystem.JENKINS_PLUGIN,
            PackageType.KbPkg: Ecosystem.MSRC_KB,
            PackageType.LinuxKernelPkg: Ecosystem.LINUX_KERNEL,
            PackageType.LinuxKernelModulePkg: Ecosystem.LINUX_KERNEL_MODULE,
            PackageType.NixPkg: Ecosystem.NIXPKGS,
            PackageType.NpmPkg: Ecosystem.NPM,
            PackageType.PhpComposerPkg: Ecosystem.PACKAGIST,
            PackageType.PhpPeclPkg: Ecosystem.PACKAGIST,
            PackageType.PhpPearPkg: Ecosystem.PACKAGIST,
            PackageType.PortagePkg: Ecosystem.PORTAGE,
            PackageType.PythonPkg: Ecosystem.PYPI,
            PackageType.Rpkg: Ecosystem.CRAN,
            PackageType.RpmPkg: Ecosystem.RPM,
            PackageType.SwiftPkg: Ecosystem.SWIFTURL,
            PackageType.WordpressPluginPkg: Ecosystem.WORDPRESS_PLUGIN,
            PackageType.UnknownPkg: None,
        }
        return package_type_to_ecosystem.get(self)


class Package(BaseModel):
    name: NonEmptyStr
    version: TrimmedNonEmptyStr
    language: Language
    locations: list[Location]
    type: PackageType
    advisories: list[Advisory] | None = None
    found_by: NonEmptyStr | None = None
    health_metadata: HealthMetadata | None = None
    ecosystem_data: AcceptedEcosystemData | None = None
    p_url: NonEmptyStr
    safe_versions: list[str] | None = None
    syft_id: NonEmptyStr | None = None

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Package):
            return False

        self_dump = cast("dict[str, object]", self.model_dump())
        other_dump = cast("dict[str, object]", other.model_dump())

        return self_dump == other_dump

    def __hash__(self) -> int:
        return hash(self.id_)

    @property
    def id_(self) -> str:
        return self.id_by_hash()

    def id_by_hash(self) -> str:
        try:
            obj_data = {
                "name": self.name,
                "version": self.version,
                "language": self.language.value,
                "type": self.type.value,
                "p_url": self.p_url,
            }
            obj_str = json.dumps(obj_data, sort_keys=True)
            return hashlib.sha256(obj_str.encode()).hexdigest()
        except Exception as exc:
            error_message = "Package data invalid for ID generation: " + json.dumps(
                obj_data, sort_keys=True
            )
            raise ValueError(error_message) from exc
