import hashlib
import json
from enum import StrEnum
from typing import Any, 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 Ecosystem(StrEnum):
    ALPM = "alpm"
    ALPINE = "alpine"
    DEBIAN = "debian"
    GO = "go"
    MAVEN = "maven"
    NPM = "npm"
    NUGET = "nuget"
    PACKAGIST = "packagist"
    PUB = "pub"
    PYPI = "pypi"
    RPM = "rpm"
    RUBYGEMS = "rubygems"
    SWIFTURL = "swifturl"


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


class Package(BaseModel):
    name: NonEmptyStr
    version: TrimmedNonEmptyStr
    language: Language
    locations: list[Location]
    ecosystem: Ecosystem
    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,
                "ecosystem": self.ecosystem.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

    def serialize(self) -> dict[str, Any]:  # type: ignore[explicit-any]
        return {
            "id": self.id_,
            "name": self.name,
            "version": self.version,
            "locations": [loc.serialize() for loc in self.locations],  # type: ignore[misc]
            "language": self.language.value,
            "ecosystem": self.ecosystem.value,
            "package_url": self.p_url,
            "found_by": self.found_by,
            "health_metadata": self.health_metadata.model_dump() if self.health_metadata else None,  # type: ignore[misc]
            "safe_versions": self.safe_versions,
            "advisories": [adv.serialize() for adv in self.advisories or []],  # type: ignore[misc]
        }
