import re
from typing import NamedTuple

from gemfileparser import GemfileParser

from labels.model.file import Location, LocationReadCloser
from labels.model.package import 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.ruby.package_builder import new_gem_package
from labels.parsers.cataloger.ruby.utils import format_ruby_version_constraints
from labels.parsers.cataloger.utils import get_enriched_location


class GemDependency(NamedTuple):
    name: str
    version_constraints: list[str]
    is_development: bool
    line: int


DEPENDENCY_PATTERN = re.compile(
    r"\w+\.add(_runtime|_development)?_dependency\s*\(?\s*"
    r"[\"']([^\"']+)[\"']"
    r"((?:\s*,\s*[\"'][^\"']+[\"'])*)"
    r"\s*\)?"
)

VERSION_CONSTRAINT_PATTERN = re.compile(r"[\"']([^\"']+)[\"']")


def parse_gemspec(
    _resolver: Resolver | None,
    _environment: Environment | None,
    reader: LocationReadCloser,
) -> tuple[list[Package], list[Relationship]]:
    content = reader.read_closer.read()
    dependencies = _extract_dependencies_by_line(content)
    packages = _build_packages_from_dependencies(dependencies, reader.location)

    return packages, []


def _extract_dependencies_by_line(content: str) -> list[GemDependency]:
    dependencies: list[GemDependency] = []

    for line_number, line in enumerate(content.splitlines(), start=1):
        preprocessed = GemfileParser.preprocess(line)
        if not preprocessed:
            continue

        dependency = _parse_dependency_line(preprocessed, line_number)
        if dependency:
            dependencies.append(dependency)

    return dependencies


def _parse_dependency_line(line: str, line_number: int) -> GemDependency | None:
    match = DEPENDENCY_PATTERN.search(line)
    if not match:
        return None

    dep_type = str(match.group(1) or "")
    is_development = dep_type == "_development"
    name = match.group(2)
    version_part = match.group(3)

    version_constraints: list[str] = VERSION_CONSTRAINT_PATTERN.findall(version_part)
    if not version_constraints:
        return None

    return GemDependency(name, version_constraints, is_development, line_number)


def _build_packages_from_dependencies(
    dependencies: list[GemDependency], base_location: Location
) -> list[Package]:
    packages: list[Package] = []

    for dep in dependencies:
        version = format_ruby_version_constraints(dep.version_constraints)
        if not version:
            continue

        location = get_enriched_location(
            base_location,
            line=dep.line,
            is_transitive=False,
            is_dev=dep.is_development,
            package_manager=PackageManager.BUNDLER,
        )

        package = new_gem_package(name=dep.name, version=version, location=location)
        if package:
            packages.append(package)

    return packages
