import re
from collections.abc import (
    Iterable,
)
from copy import (
    deepcopy,
)

from gemfileparser import (
    Dependency,
    GemfileParser,
)

from labels.artifact.relationship import (
    Relationship,
)
from labels.file.dependency_type import (
    DependencyType,
)
from labels.file.location import (
    Location,
)
from labels.file.location_read_closer import (
    LocationReadCloser,
)
from labels.file.resolver import (
    Resolver,
)
from labels.file.scope import (
    Scope,
)
from labels.model.core import (
    Language,
    Package,
    PackageType,
)
from labels.pkg.cataloger.generic.parser import (
    Environment,
)

from .package import (
    package_url,
)

GEMFILE_DEP: re.Pattern[str] = re.compile(
    r'^\s*(?P<gem>gem ".*?",?( "[><~=]{0,2}\s?[\d\.]+",?){0,2})',
)
NOT_PROD_DEP: re.Pattern[str] = re.compile(
    r":group => \[?[:\w\-, ]*(:development|:test)",
)
NOT_PROD_GROUP: re.Pattern[str] = re.compile(r"(\s*)group :(test|development)")
GEM_LOCK_DEP: re.Pattern[str] = re.compile(
    r"^\s{4}(?P<gem>[^\s]*)\s\([^\d]*(?P<version>.*)\)$",
)


def _is_dev_dependency(line: str) -> bool:
    return bool(NOT_PROD_DEP.search(line))


def format_requirements(requirements: list[str]) -> str:
    formatted: str = ""
    if len(requirements) == 0:
        return formatted
    requirements = [req.replace(" ", "") for req in requirements]
    first_req = requirements[0]
    if "~" in first_req:
        if len(requirements) == 1:
            dot_times = first_req.count(".")
            if dot_times <= 1:
                formatted = first_req.replace("~>", "^")
            else:
                formatted = first_req.replace("~>", "~")
        elif len(requirements) == 2:
            sec_req = requirements[1]
            if ">=" in sec_req:
                formatted = sec_req.replace(">=", "^")
            elif "!=" in sec_req:
                sec_ver = sec_req.replace("!=", "")
                first_ver = first_req.replace("!=", "")
                formatted = f">={first_ver} <{sec_ver}  || >{sec_ver}"
    else:
        formatted = " ".join(requirements)
    return formatted


def match_dep_criteria(
    column_list: Iterable[str],
) -> Dependency:
    dep = Dependency()
    for column in column_list:
        for criteria, criteria_regex in GemfileParser.gemfile_regexes.items():
            match = criteria_regex.match(column)
            if match:
                if criteria == "requirement":
                    dep.requirement.append(match.group(criteria))
                else:
                    setattr(dep, criteria, match.group(criteria))
                break
    return dep


def parse_line(in_line: str, *, gem_file: bool) -> tuple[str, str]:
    line: list[str] = []
    if gem_file:
        line = in_line.split(",")
    else:
        line = in_line.split(" ", maxsplit=1)
        if len(line) > 1 and (", " in line[1]):
            line = [line[0], *line[1].split(", ")]

    column_list: list[str] = []
    for column in line:
        stripped_column = (
            column.replace("'", "")
            .replace('"', "")
            .replace("%q<", "")
            .replace("(", "")
            .replace(")", "")
            .replace("[", "")
            .replace("]", "")
            .strip()
        )
        column_list.append(stripped_column)

    deps = match_dep_criteria(column_list)
    deps_dict = deps.to_dict()
    product: str = deps_dict["name"]
    version: str = format_requirements(deps_dict["requirement"])
    return product, version


def create_dependency(
    line_number: int,
    location: Location,
    matched: re.Match[str],
    *,
    is_dev: bool = False,
) -> Package | None:
    gem_info = GemfileParser.preprocess(matched.group("gem"))[3:]
    product, version = parse_line(gem_info, gem_file=True)
    new_location = deepcopy(location)
    new_location.scope = Scope.DEV if is_dev else Scope.PROD
    if new_location.coordinates:
        new_location.coordinates.line = line_number
        new_location.dependency_type = DependencyType.DIRECT
    if not version:
        return None
    return Package(
        name=product,
        version=version,
        type=PackageType.GemPkg,
        locations=[new_location],
        p_url=package_url(product, version),
        metadata=None,
        language=Language.RUBY,
        licenses=[],
        is_dev=is_dev,
    )


def parse_gemfile(
    _: Resolver | None,
    __: Environment | None,
    reader: LocationReadCloser,
) -> tuple[list[Package], list[Relationship]]:
    packages = []
    in_group_block = False
    end_line = ""

    for line_number, line in enumerate(
        reader.read_closer.read().splitlines(),
        1,
    ):
        if in_group_block:
            if line == end_line:
                in_group_block = False
            elif (
                (matched := GEMFILE_DEP.search(line))
                and matched
                and (
                    dependency := create_dependency(
                        line_number,
                        reader.location,
                        matched,
                        is_dev=_is_dev_dependency(line),
                    )
                )
            ):
                packages.append(dependency)
            continue

        if match_group := NOT_PROD_GROUP.search(line):
            in_group_block = True
            end_line = f"{match_group.group(1)}end"
            continue

        if (
            (matched := GEMFILE_DEP.search(line))
            and matched
            and (
                dependency := create_dependency(
                    line_number,
                    reader.location,
                    matched,
                    is_dev=_is_dev_dependency(line),
                )
            )
        ):
            packages.append(dependency)

    return packages, []
