import operator
import re
from typing import Self


class Version:
    def __init__(self, major: int, minor: int, patch: int | str | None = None) -> None:
        self.major = major
        self.minor = minor
        self.patch = patch

    def __hash__(self) -> int:
        return hash((self.major, self.minor, self.patch))

    @property
    def patch_or_0(self) -> int | str:
        return self.patch or 0

    @classmethod
    def from_string(cls, version_string: str) -> Self | str:
        semver_pattern = re.compile(
            r".*?(?P<major>[0-9]+)\.(?P<minor>[0-9]+)(\.(?P<patch>.*))?"
            ""
        )
        search_result = re.search(semver_pattern, version_string)
        patch: str | int | None
        if search_result:
            gd = search_result.groupdict()
            _patch = gd.get("patch")
            try:
                patch = int(_patch or "")
            except ValueError:
                patch = _patch
            except TypeError:
                patch = _patch

            return cls(int(gd["major"]), int(gd["minor"]), patch)
        return version_string

    def __str__(self) -> str:
        return f"{self.major}.{self.minor}.{self.patch}"

    def __repr__(self) -> str:
        return str(self)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Version):
            raise TypeError
        return (
            self.major == other.major
            and self.minor == other.minor
            and self.patch_or_0 == other.patch_or_0
        )

    def minor_equal(self, other: "Version") -> bool:
        return self.major == other.major and self.minor == other.minor

    def meets_constraint(self, constraint: str) -> bool:
        pattern: re.Pattern[str] = re.compile(r"^ *(?P<op>\^|[\>\<\=]\=?) *(?P<version>.+) *")
        for segment in re.split(r" *, *", constraint):
            if not (search := re.search(pattern, segment)):
                raise ValueError(segment)
            gd = search.groupdict()
            if not isinstance(ver := Version.from_string(gd["version"]), Version):
                raise TypeError(gd["version"])
            op = {
                "==": operator.eq,
                "=": operator.eq,
                ">": operator.gt,
                "<": operator.lt,
                ">=": operator.ge,
                "<=": operator.le,
            }[gd["op"]]
            if not op(self, ver):
                return False

        return True

    def distance(self, other: "Version") -> str:
        def _distance(a: Version, b: Version) -> str:
            def replace_negative(i: int) -> str:
                return "?" if i < 0 else str(i)

            major = replace_negative(b.major - a.major)
            minor = replace_negative(b.minor - a.minor)
            try:
                if b.patch_or_0 == a.patch_or_0:
                    _patch = 0
                else:
                    _patch = b.patch_or_0 - a.patch_or_0  # type: ignore
                patch = replace_negative(_patch)
            except TypeError:
                patch = "?"
            return f"{major}.{minor}.{patch}"

        if self > other:
            return _distance(other, self)
        elif other > self:
            return _distance(self, other)
        return "?.?.?"

    def __sub__(self: Self, other: "Version") -> "Version":
        try:
            patch_diff = self.patch_or_0 - other.patch_or_0  # type: ignore
        except TypeError:
            patch_diff = "?"
        return self.__class__(
            self.major - other.major,
            self.minor - other.minor,
            patch_diff,
        )

    def __ge__(self, other: "Version") -> bool:
        if (self.major, self.minor, self.patch_or_0) < (other.major, other.minor, self.patch_or_0):
            return False
        if self.minor_equal(other) and (type(self.patch_or_0) != type(self.patch_or_0)):  # noqa: E721
            return False
        return (self.major, self.minor, self.patch_or_0) >= (
            other.major,
            other.minor,
            other.patch_or_0,
        )

    def __gt__(self, other: "Version") -> bool:
        if (self.major, self.minor, str(self.patch_or_0)) <= (
            other.major,
            other.minor,
            str(other.patch_or_0),
        ):
            return False
        if self.minor_equal(other):
            if type(self.patch_or_0) != type(other.patch_or_0):  # noqa: E721
                return False

        return (self.major, self.minor, self.patch_or_0) > (
            other.major,
            other.minor,
            other.patch_or_0,
        )
