from more_itertools import flatten

from labels.model.file import Location, LocationReadCloser
from labels.model.indexables import IndexedDict, ParsedValue
from labels.model.package import Ecosystem, Package
from labels.model.package_manager import PackageManager
from labels.model.relationship import Relationship
from labels.model.release import Environment
from labels.model.resolver import Resolver
from labels.parsers.cataloger.dotnet.package_builder import new_dotnet_package
from labels.parsers.cataloger.dotnet.utils import build_relationships_from_json_dependencies
from labels.parsers.cataloger.utils import get_enriched_location
from labels.parsers.collection.json import parse_json_with_tree_sitter
from labels.utils.strings import normalize_name


def parse_dotnet_deps_json(
    _resolver: Resolver | None,
    _environment: Environment | None,
    reader: LocationReadCloser,
) -> tuple[list[Package], list[Relationship]]:
    file_content = parse_json_with_tree_sitter(reader.read_closer.read())
    if not isinstance(file_content, IndexedDict):
        return [], []

    targets = _collect_targets(file_content)
    if not targets:
        return [], []

    packages = _collect_packages(targets, reader.location)
    relationships = _collect_relationships(targets, packages)

    return packages, relationships


def _collect_targets(
    file_content: IndexedDict[str, ParsedValue],
) -> list[tuple[str, ParsedValue]] | None:
    targets: ParsedValue = file_content.get("targets")
    if not isinstance(targets, IndexedDict):
        return None

    return list(
        flatten(target.items() for target in targets.values() if isinstance(target, IndexedDict))
    )


def _collect_packages(targets: list[tuple[str, ParsedValue]], location: Location) -> list[Package]:
    packages: list[Package] = []

    for package_key, package_value in targets:
        if not isinstance(package_value, IndexedDict):
            continue

        package_info = _get_validated_package_info(package_key)
        if not package_info:
            continue

        name, version = package_info
        new_location = get_enriched_location(
            location,
            line=package_value.position.start.line,
            package_manager=PackageManager.NUGET,
        )

        new_package = new_dotnet_package(name, version, new_location)
        if new_package:
            packages.append(new_package)

    return packages


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

    packages_by_key = {(pkg.name, pkg.version): pkg for pkg in packages}

    for package_key, package_value in targets:
        relationships.extend(
            _build_relationships_for_package(package_key, package_value, packages_by_key)
        )

    return relationships


def _build_relationships_for_package(
    package_key: str,
    package_value: ParsedValue,
    packages_by_key: dict[tuple[str, str], Package],
) -> list[Relationship]:
    relationships: list[Relationship] = []

    if not isinstance(package_value, IndexedDict):
        return relationships

    package_info = _get_validated_package_info(package_key)
    if not package_info:
        return relationships

    raw_name, version = package_info
    normalized_name = normalize_name(raw_name, Ecosystem.NUGET)

    current_package = packages_by_key.get((normalized_name, version))
    if not isinstance(current_package, Package):
        return relationships

    dependencies = package_value.get("dependencies")
    if not isinstance(dependencies, IndexedDict):
        return relationships

    return build_relationships_from_json_dependencies(
        dependencies, current_package, packages_by_key
    )


def _get_validated_package_info(package_key: str) -> tuple[str, str] | None:
    if not _is_valid_package_key(package_key):
        return None

    return _split_package_key_into_name_and_version(package_key)


def _is_valid_package_key(package_key: str) -> bool:
    return isinstance(package_key, str) and "/" in package_key


def _split_package_key_into_name_and_version(package_key: str) -> tuple[str, str]:
    name, version = package_key.split("/", 1)
    return name, version
