from collections.abc import Iterable

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_poetry_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 _select_hash(files: IndexedList[ParsedValue]) -> str | None:
    selected_hash: str | None = None
    for entry in files:
        if not isinstance(entry, IndexedDict):
            continue
        filename = str(entry.get("file", ""))
        hash_value = str(entry.get("hash", ""))
        if not hash_value:
            continue
        if filename.endswith(".tar.gz"):
            return hash_value
        if selected_hash is None and filename.endswith(".whl"):
            selected_hash = hash_value
    return selected_hash


def _extract_integrity(raw_package: IndexedDict[str, ParsedValue]) -> HealthMetadata | None:
    files = raw_package.get("files")
    if not isinstance(files, IndexedList):
        return None

    selected_hash = _select_hash(files)
    if not selected_hash or ":" not in 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_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.POETRY,
            )
            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 _get_dependencies(
    package: ParsedValue,
    packages: list[Package],
) -> tuple[Package | None, IndexedDict[str, ParsedValue]] | None:
    if not isinstance(package, IndexedDict):
        return None

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

    _pkg = _find_package_by_name(packages, package_name)

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

    return _pkg, deps


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


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

    if not isinstance(toml_pkgs, IndexedList):
        return []

    for package in toml_pkgs:
        pkg_with_deps = _get_dependencies(package, packages)

        if not pkg_with_deps:
            continue

        pkg, deps = pkg_with_deps

        if not pkg or not deps:
            continue

        relationships.extend(_create_relationships_for_package(pkg, deps.keys(), packages))

    return relationships


def _create_relationships_for_package(
    pkg: Package, dependency_names: Iterable[str], packages: list[Package]
) -> list[Relationship]:
    relationships = []
    for dep_name in dependency_names:
        dep_pkg = _find_package_by_name(packages, dep_name)
        if dep_pkg:
            relationships.append(
                Relationship(
                    from_=dep_pkg.id_,
                    to_=pkg.id_,
                    type=RelationshipType.DEPENDENCY_OF_RELATIONSHIP,
                )
            )
    return relationships
