import logging

from spdx_tools.spdx.model.checksum import Checksum, ChecksumAlgorithm
from spdx_tools.spdx.model.document import Document
from spdx_tools.spdx.model.package import (
    ExternalPackageRef,
    ExternalPackageRefCategory,
    PackagePurpose,
)
from spdx_tools.spdx.model.package import Package as SPDX_Package
from spdx_tools.spdx.model.spdx_no_assertion import SpdxNoAssertion
from spdx_tools.spdx.validation.uri_validators import validate_url

from labels.model.file import Location
from labels.model.metadata import HealthMetadata
from labels.model.package import Package
from labels.output.utils import sanitize_name, sanitize_spdx_path

NOASSERTION = SpdxNoAssertion()
NAMESPACE = "fluid-attacks"

LOGGER = logging.getLogger(__name__)


def is_valid_url(url: str) -> bool:
    return not validate_url(url)


def get_valid_advisory_urls(url: str) -> str:
    if url and url != "MANUAL":
        if is_valid_url(url):
            return url
        LOGGER.warning("Invalid advisory URL found and skipped: %s", url)

    return "https://fluidattacks.com"


def add_vulnerabilities_spdx(package: Package) -> list[ExternalPackageRef]:
    vulnerabilities_refs = []
    if package.advisories:
        for advisory in package.advisories:
            combined_urls = get_valid_advisory_urls(advisory.source)

            comment_parts = [
                f"Severity: {advisory.severity_level}",
                f"EPSs: {advisory.epss}",
                f"Score: {advisory.percentile}",
                f"Affected Version: {package.version}",
                f"Affected Version Range: {advisory.vulnerable_version}"
                if advisory.vulnerable_version
                else None,
                f"Description: {advisory.details}" if advisory.details else None,
                f"KEV Catalog: {advisory.kev_catalog}",
            ]
            comment_text = "; ".join(filter(None, comment_parts))

            vulnerabilities_refs.append(
                ExternalPackageRef(
                    category=ExternalPackageRefCategory.SECURITY,
                    reference_type="advisory",
                    locator=combined_urls,
                    comment=comment_text,
                ),
            )

    return vulnerabilities_refs


def add_locations_external_refs(
    locations: list[Location],
) -> list[ExternalPackageRef]:
    locations_external_refs = []
    for idx, location in enumerate(locations):
        path = sanitize_spdx_path(location.path())
        coordinates = location.coordinates
        line = coordinates.line if coordinates else None
        layer = coordinates.file_system_id if coordinates else None

        locations_external_refs.append(
            ExternalPackageRef(
                category=ExternalPackageRefCategory.OTHER,
                reference_type=f"{NAMESPACE}:locations:{idx}:path",
                locator=path,
            ),
        )

        if line:
            locations_external_refs.append(
                ExternalPackageRef(
                    category=ExternalPackageRefCategory.OTHER,
                    reference_type=f"{NAMESPACE}:locations:{idx}:line",
                    locator=str(line),
                ),
            )

        if layer:
            locations_external_refs.append(
                ExternalPackageRef(
                    category=ExternalPackageRefCategory.OTHER,
                    reference_type=f"{NAMESPACE}:locations:{idx}:layer",
                    locator=layer,
                ),
            )

    return locations_external_refs


def add_purl_external_ref(
    package_purl: str,
) -> ExternalPackageRef:
    return ExternalPackageRef(
        category=ExternalPackageRefCategory.PACKAGE_MANAGER,
        reference_type="purl",
        locator=package_purl,
    )


def add_language_external_ref(language: str) -> ExternalPackageRef:
    return ExternalPackageRef(
        category=ExternalPackageRefCategory.OTHER,
        reference_type=f"{NAMESPACE}:language",
        locator=language,
    )


def add_external_refs(package: Package) -> list[ExternalPackageRef]:
    external_refs = []

    external_refs.append(add_language_external_ref(package.language))

    external_refs.append(add_purl_external_ref(package.p_url))
    external_refs.extend(add_locations_external_refs(package.locations))
    external_refs.extend(add_vulnerabilities_spdx(package))

    return external_refs


def add_integrity(health_metadata: HealthMetadata | None) -> list[Checksum]:
    if health_metadata:
        integrity = health_metadata.artifact.integrity if health_metadata.artifact else None
        if integrity and integrity.algorithm and integrity.value:
            return [
                Checksum(
                    algorithm=ChecksumAlgorithm[integrity.algorithm.upper()],
                    value=integrity.value,
                ),
            ]
    return []


def add_empty_package(document: Document) -> None:
    document.creation_info.document_comment = (
        "No packages or relationships were found in the resource."
    )

    empty_package = SPDX_Package(
        name="NONE",
        spdx_id="SPDXRef-Package-NONE",
        download_location=NOASSERTION,
        primary_package_purpose=PackagePurpose.LIBRARY,
    )

    document.packages = [empty_package]
    document.relationships = []


def get_spdx_id(package: Package) -> str:
    name = sanitize_name(package.name)
    pkg_platform = sanitize_name(package.ecosystem.value)
    pkg_id = package.id_
    return f"SPDXRef-Package-{pkg_platform}-{name}-{pkg_id}"
