from labels.model.file import LocationReadCloser
from labels.model.indexables import IndexedDict, IndexedList, ParsedValue
from labels.model.metadata import Artifact, Digest, HealthMetadata
from labels.model.package import Ecosystem, Package
from labels.model.package_manager import PackageManager
from labels.model.relationship import Relationship, RelationshipType
from labels.model.release import Environment
from labels.model.resolver import Resolver
from labels.parsers.cataloger.python.package_builder import new_python_package
from labels.parsers.cataloger.utils import get_enriched_location
from labels.parsers.collection import toml
from labels.utils.strings import normalize_name


def parse_uv_lock(
    _resolver: Resolver | None,
    _environment: Environment | None,
    reader: LocationReadCloser,
) -> tuple[list[Package], list[Relationship]]:
    file_content = toml.parse_toml_with_tree_sitter(reader.read_closer.read())

    packages = _collect_packages(file_content, reader)
    relationships = _collect_relationships(file_content, packages)

    return packages, relationships


def _collect_packages(
    file_content: IndexedDict[str, ParsedValue],
    reader: LocationReadCloser,
) -> list[Package]:
    packages = []
    toml_pkgs = file_content.get("package")
    if not isinstance(toml_pkgs, IndexedList):
        return []

    for raw_package in toml_pkgs:
        if not isinstance(raw_package, IndexedDict):
            continue

        name = str(raw_package.get("name", ""))
        version = str(raw_package.get("version", ""))

        if not version:
            continue

        new_location = (
            get_enriched_location(
                reader.location,
                line=raw_package.get_key_position("version").start.line,
                package_manager=PackageManager.UV,
            )
            if isinstance(raw_package, IndexedDict)
            else reader.location
        )

        package = new_python_package(name=name, version=version, location=new_location)
        if package:
            package.health_metadata = _extract_integrity(raw_package)
            packages.append(package)

    return packages


def _extract_hash(source: ParsedValue) -> str | None:
    if isinstance(source, IndexedDict):
        hash_value = source.get("hash")
        if isinstance(hash_value, str) and ":" in hash_value:
            return hash_value
    return None


def _extract_integrity(raw_package: IndexedDict[str, ParsedValue]) -> HealthMetadata | None:
    sdist = raw_package.get("sdist")
    selected_hash = _extract_hash(sdist)

    if not selected_hash:
        wheels = raw_package.get("wheels")
        if isinstance(wheels, IndexedList):
            for wheel in wheels:
                selected_hash = _extract_hash(wheel)
                if selected_hash:
                    break

    if not selected_hash:
        return None

    algorithm, digest_value = selected_hash.split(":", 1)
    if not algorithm or not digest_value:
        return None

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


def _collect_relationships(
    toml_content: IndexedDict[str, ParsedValue],
    packages: list[Package],
) -> list[Relationship]:
    relationships: list[Relationship] = []

    toml_pkgs = toml_content.get("package")
    if not isinstance(toml_pkgs, IndexedList):
        return relationships

    for package in toml_pkgs:
        dep_info = _get_dependencies(package, packages)
        if not dep_info:
            continue

        source_pkg, deps = dep_info
        if not source_pkg:
            continue

        for dep in deps:
            dep_name = _extract_dependency_name(dep)
            if not dep_name:
                continue

            dep_pkg = _find_package_by_name(packages, dep_name)
            if dep_pkg:
                relationships.append(
                    Relationship(
                        from_=dep_pkg.id_,
                        to_=source_pkg.id_,
                        type=RelationshipType.DEPENDENCY_OF_RELATIONSHIP,
                    ),
                )

    return relationships


def _get_dependencies(
    package: ParsedValue,
    packages: list[Package],
) -> tuple[Package | None, IndexedList[ParsedValue]] | None:
    if not isinstance(package, IndexedDict):
        return None

    package_name = package.get("name")
    if not isinstance(package_name, str):
        return None

    package_found = _find_package_by_name(packages, package_name)

    dependencies = package.get("dependencies")
    if not isinstance(dependencies, IndexedList):
        return None

    return package_found, dependencies


def _extract_dependency_name(dep: ParsedValue) -> str | None:
    if not isinstance(dep, IndexedDict):
        return None

    dep_name = dep.get("name")
    return str(dep_name) if isinstance(dep_name, str) else None


def _find_package_by_name(packages: list[Package], name: str) -> Package | None:
    normalized_package_name = normalize_name(name, Ecosystem.PYPI)
    return next((pkg for pkg in packages if pkg.name == normalized_package_name), None)
