import fnmatch
import sys
import traceback
from typing import Iterable, Tuple, List

from packaging.version import Version, parse, LegacyVersion

from mach_nix.cache import cached


class PyVer(Version):
    def nix(self):
        res = 'python'
        res += str(self.release[0])
        if len(self.release) >= 2:
            res += str(self.release[1])
        return res

    def digits(self):
        return ''.join(filter(lambda c: c.isdigit(), str(self)))[:2]

    def python_version(self):
        return f"{self.release[0]}.{self.release[1]}"

    def python_full_version(self):
        try:
            return f"{self.release[0]}.{self.release[1]}.{self.release[2]}"
        except IndexError:
            traceback.print_exc()
            print("Error: please specify full python version including bugfix version (like 3.7.5)", file=sys.stderr)
            exit(1)


def ver_better_than_other(v: Version, o: Version) -> bool:
    # print(inspect.getfile(v.__class__))
    instability = {v: 0, o: 0}
    if v >= o:
        for ver in [v, o]:
            if ver.dev:
                instability[ver] += 2
            if ver.pre:
                instability[ver] += 1
        if instability[v] <= instability[o]:
            return True
    return False


def ver_lt_other(v: Version, o: Version) -> bool:
    return not (ver_better_than_other(v, o) or v == o)


def ver_sort_key(ver: Version):
    """
    For sorting versions by preference in reversed order. (last elem == highest preference)
    """
    if isinstance(ver, LegacyVersion):
        return 0, 0, 0, ver
    is_dev = int(ver.is_devrelease)
    is_pre = int(ver.is_prerelease)
    return 1, not is_dev, not is_pre, ver


def best_version(versions: Iterable[Version]) -> Version:
    best = None
    for ver in versions:
        if best is None:
            best = ver
            continue
        if ver_better_than_other(ver, best):
            best = ver
    return best


@cached(keyfunc=lambda args: tuple(args[0]) + tuple(args[1]))
def filter_versions(
        versions: Iterable[Version],
        specs: Iterable[Tuple[str, str]]) -> List[Version]:
    """
    Reduces a given list of versions to contain only versions
    which are allowed according to the given specifiers
    """
    versions = list(versions)
    for op, ver in specs:
        ver = parse(ver)
        if op == '==':
            versions_str = (str(ver) for ver in versions)
            versions_str_filtered = list(ver_str for ver_str in fnmatch.filter(versions_str, str(ver)))
            versions = [ver for ver in versions if str(ver) in versions_str_filtered]
        elif op == '!=':
            versions_str = (str(ver) for ver in versions)
            bad_versions_str = set(fnmatch.filter(versions_str, str(ver)))
            versions = list(filter(lambda v: str(v) not in bad_versions_str, versions))
        else:
            versions = list(filter(lambda v: eval(f'v {op} ver', dict(v=v, ver=ver)), versions))
    return list(versions)

