import base64
import logging
import re
from urllib.parse import parse_qs

from labels.model.file import DependencyType, Location, Scope
from labels.model.metadata import Artifact, Digest, HealthMetadata
from labels.model.package_manager import PackageManager
from labels.model.release import Release
from labels.utils.strings import format_exception

LOGGER = logging.getLogger(__name__)

PURL_QUALIFIER_DISTRO = "distro"

MALFORMED_PACKAGE_WARNING = (
    "Malformed package. Required fields are missing or data types are incorrect."
)


def purl_qualifiers(
    qualifiers: dict[str, str | None],
    release: Release | None = None,
) -> dict[str, str]:
    if release:
        distro_qualifiers = []
        if release.id_:
            distro_qualifiers.append(release.id_)
        if release.version_id:
            distro_qualifiers.append(release.version_id)
        elif release.build_id:
            distro_qualifiers.append(release.build_id)

        if distro_qualifiers:
            qualifiers[PURL_QUALIFIER_DISTRO] = "-".join(distro_qualifiers)

    return {
        key: qualifiers.get(key, "") or ""
        for key in sorted(qualifiers.keys())
        if qualifiers.get(key)
    }


def _get_distro_params(distro_param: str | None) -> tuple[str | None, str | None]:
    if distro_param is None:
        return None, None

    parts = distro_param.rsplit("-", 1)

    if len(parts) == 2:
        return parts[0], parts[1]

    return parts[0], None


def extract_distro_info(pkg_str: str) -> tuple[str | None, str | None, str | None]:
    parts = pkg_str.split("?", 1)
    if len(parts) != 2:
        return None, None, None

    query = parts[1]
    params = parse_qs(query)
    distro_id, distro_version = _get_distro_params(params.get("distro", [None])[0])
    arch = params.get("arch", [None])[0]
    return distro_id, distro_version, arch


def get_enriched_location(
    base: Location,
    *,
    line: int | None = None,
    is_transitive: bool | None = None,
    is_dev: bool | None = None,
    package_manager: PackageManager = PackageManager.UNKNOWN,
) -> Location:
    update_data: dict[str, object] = {}

    update_data["package_manager"] = package_manager

    if is_dev is not None:
        scope: Scope = Scope.BUILD if is_dev else Scope.RUN
        update_data["scope"] = scope

    if is_transitive is not None:
        dependency_type: DependencyType = (
            DependencyType.TRANSITIVE if is_transitive else DependencyType.DIRECT
        )
        update_data["dependency_type"] = dependency_type

    if line is not None and base.coordinates:
        line_update = {"line": line}
        coordinates = base.coordinates.model_copy(update=line_update)
        update_data["coordinates"] = coordinates

    return base.model_copy(deep=True, update=update_data)


INTEGRITY_PATTERN = re.compile(r"^(sha(?:1|256|384|512))-(.+)\Z")

_HEX_LENGTH_TO_ALGORITHM: dict[int, str] = {
    40: "sha1",
    64: "sha256",
    96: "sha384",
    128: "sha512",
}


def _decode_integrity(digest_str: str) -> tuple[str, str] | None:
    match = INTEGRITY_PATTERN.match(digest_str)
    if not match:
        return None
    _, encoded = match.group(1), match.group(2)
    try:
        hex_value = base64.b64decode(encoded).hex()
    except Exception:  # noqa: BLE001
        return None
    inferred = _HEX_LENGTH_TO_ALGORITHM.get(len(hex_value))
    if not inferred:
        return None
    return inferred, hex_value


def parse_integrity_to_health_metadata(
    integrity_str: str | None,
) -> HealthMetadata | None:
    if not integrity_str:
        return None

    decoded = _decode_integrity(integrity_str.strip())
    if not decoded:
        hex_value = integrity_str.strip()
        if not all(c in "0123456789abcdef" for c in hex_value.lower()):
            return None
        algorithm = _HEX_LENGTH_TO_ALGORITHM.get(len(hex_value))
        if not algorithm:
            return None
        decoded = algorithm, hex_value

    algorithm, hex_value = decoded
    return HealthMetadata(
        artifact=Artifact(
            integrity=Digest(algorithm=algorithm, value=hex_value),
        ),
    )


def log_malformed_package_warning(
    location: Location,
    exception: Exception,
) -> None:
    LOGGER.warning(
        MALFORMED_PACKAGE_WARNING,
        extra={
            "extra": {
                "exception": format_exception(str(exception)),
                "location": location.path(),
            },
        },
    )
