import re
from collections.abc import Callable

from labels.model.package import Ecosystem

UNSTABLE_SEMVER_KEYWORDS: frozenset[str] = frozenset(
    {
        "alpha",
        "beta",
        "canary",
        "cr",
        "dev",
        "milestone",
        "next",
        "nightly",
        "pre",
        "preview",
        "rc",
        "snapshot",
    }
)

STABLE_QUALIFIERS: frozenset[str] = frozenset(
    {
        "final",
        "ga",
        "ga.final",
        "ga.release",
        "lts",
        "release",
        "release.final",
        "stable",
    }
)

_OS_ECOSYSTEMS: frozenset[Ecosystem] = frozenset(
    {
        Ecosystem.ALPM,
        Ecosystem.ALPINE,
        Ecosystem.DEBIAN,
        Ecosystem.RPM,
    }
)

_SEMVER_PRE_RELEASE_RE = re.compile(r"^\d+(?:\.\d+)*-(.+?)(?:\+.*)?\Z")
_SEMVER_QUALIFIER_RE = re.compile(r"^\d+(?:\.\d+)*\.([a-zA-Z][a-zA-Z0-9.]*)\Z")

_PEP440_PRE_RE = re.compile(
    r"^\d+(?:\.\d+)*"
    r"(?:\.?(?:a|alpha|b|beta|c|rc)\d*)"
    r"(?:\..*)?(?:\+.*)?\Z",
    re.IGNORECASE,
)
_PEP440_DEV_RE = re.compile(
    r"^\d+(?:\.\d+)*\.?dev\d*(?:\+.*)?\Z",
    re.IGNORECASE,
)

_RUBY_PRE_RE = re.compile(
    r"^\d+(?:\.\d+)*\."
    r"(?:alpha|beta|dev|pre|rc)\d*(?:\.\d+)*\Z",
    re.IGNORECASE,
)

_M_DIGIT_RE = re.compile(r"^m\d+\Z", re.IGNORECASE)


def _is_unstable_keyword(segment: str) -> bool | None:
    if segment in STABLE_QUALIFIERS:
        return False
    if segment in UNSTABLE_SEMVER_KEYWORDS:
        return True
    if _M_DIGIT_RE.match(segment):
        return True
    base = segment.rstrip("0123456789")
    if base and base != segment and base in UNSTABLE_SEMVER_KEYWORDS:
        return True
    return None


def _check_pre_release(pre_release: str) -> bool:
    normalized = pre_release.lower().replace("-", ".").replace("_", ".")
    return any(_is_unstable_keyword(seg) is True for seg in normalized.split("."))


def _is_unstable_semver(version: str) -> bool:
    match = _SEMVER_PRE_RELEASE_RE.match(version)
    if match:
        return _check_pre_release(match.group(1))

    match = _SEMVER_QUALIFIER_RE.match(version)
    if match:
        qualifier: str = match.group(1).lower()
        result = _is_unstable_keyword(qualifier)
        if result is not None:
            return result
        first_segment: str = qualifier.split(".")[0]
        return _is_unstable_keyword(first_segment) or False

    return False


def _is_unstable_python(version: str) -> bool:
    if _PEP440_DEV_RE.match(version):
        return True
    return bool(_PEP440_PRE_RE.match(version))


def _is_unstable_ruby(version: str) -> bool:
    return bool(_RUBY_PRE_RE.match(version))


def _is_unstable_generic(version: str) -> bool:
    if _is_unstable_semver(version):
        return True
    if _is_unstable_python(version):
        return True
    return _is_unstable_ruby(version)


_APP_ECOSYSTEM_DETECTOR: dict[Ecosystem, Callable[[str], bool]] = {
    Ecosystem.PACKAGIST: _is_unstable_semver,
    Ecosystem.RUBYGEMS: _is_unstable_ruby,
    Ecosystem.GO: _is_unstable_semver,
    Ecosystem.MAVEN: _is_unstable_semver,
    Ecosystem.NPM: _is_unstable_semver,
    Ecosystem.NUGET: _is_unstable_semver,
    Ecosystem.PYPI: _is_unstable_python,
    Ecosystem.PUB: _is_unstable_semver,
    Ecosystem.SWIFTURL: _is_unstable_semver,
}


def is_unstable_version(version: str, ecosystem: Ecosystem | None = None) -> bool:
    if not version or "${" in version:
        return False

    cleaned = version.strip().removeprefix("v").removeprefix("V")
    if not cleaned:
        return False

    if ecosystem is not None:
        if ecosystem in _OS_ECOSYSTEMS:
            return False
        detector = _APP_ECOSYSTEM_DETECTOR.get(ecosystem)
        if detector is not None:
            return detector(cleaned)
        return False

    return _is_unstable_generic(cleaned)
