"""Version comparison utilities.

Normalizes Gitea-style tags (v0.3.0-alpha.1) to PEP 440 (0.3.0a1)
and compares them using a tuple-based ordering. Deliberately does
not depend on the external ``packaging`` library — it's just one
more dependency to ship in the portable installer for a handful
of simple comparisons we can do with stdlib.
"""

import logging
import re
from typing import NamedTuple

logger = logging.getLogger(__name__)


class InvalidVersion(ValueError):
    """Raised when a version string cannot be parsed."""


# Release-kind ordering — smaller = earlier.
# dev < alpha < beta < rc < release
_KIND_DEV = 0
_KIND_ALPHA = 1
_KIND_BETA = 2
_KIND_RC = 3
_KIND_RELEASE = 4


class Version(NamedTuple):
    """Comparable version tuple. Larger tuples are newer versions."""

    major: int
    minor: int
    patch: int
    kind: int
    pre_num: int


_PRE_MAP = {
    "alpha": _KIND_ALPHA,
    "a": _KIND_ALPHA,
    "beta": _KIND_BETA,
    "b": _KIND_BETA,
    "rc": _KIND_RC,
}

# Matches "1.2.3" optionally followed by a pre-release segment.
# Accepts Gitea-style ("1.2.3-alpha.1") and PEP 440 ("1.2.3a1", "1.2.3.dev0").
_VERSION_PATTERN = re.compile(
    r"""
    ^
    (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
    (?:
        [-.]?                                   # optional separator
        (?P<pre_label>alpha|beta|rc|a|b|dev)    # pre-release label
        [.-]?                                   # optional separator
        (?P<pre_num>\d+)                        # pre-release number
    )?
    $
    """,
    re.IGNORECASE | re.VERBOSE,
)


def normalize_version(raw: str) -> Version:
    """Parse a version string into a comparable ``Version`` tuple.

    Accepts Gitea-style tags (``v0.3.0-alpha.1``) and PEP 440
    (``0.3.0a1``, ``0.0.0.dev0``). Raises ``InvalidVersion`` if
    the string cannot be parsed.
    """
    cleaned = raw.lstrip("vV").strip()
    m = _VERSION_PATTERN.match(cleaned)
    if not m:
        raise InvalidVersion(f"Unparseable version: {raw!r}")

    major = int(m.group("major"))
    minor = int(m.group("minor"))
    patch = int(m.group("patch"))

    pre_label = m.group("pre_label")
    pre_num_str = m.group("pre_num")

    if pre_label is None:
        kind = _KIND_RELEASE
        pre_num = 0
    else:
        label = pre_label.lower()
        if label == "dev":
            kind = _KIND_DEV
        else:
            kind = _PRE_MAP[label]
        pre_num = int(pre_num_str) if pre_num_str is not None else 0

    return Version(major, minor, patch, kind, pre_num)


def is_newer(candidate: str, current: str) -> bool:
    """Return True if *candidate* is strictly newer than *current*.

    Returns False if either version string is unparseable.
    """
    try:
        return normalize_version(candidate) > normalize_version(current)
    except InvalidVersion as e:
        logger.debug("Unparseable version string in comparison: %s", e)
        return False
