import ubelt as ub


def parse_requirement_file(fpath):
    # import requirements
    # with open('requirements.txt', 'r') as fd:
    #     for req in requirements.parse(fd):
    #         print(req.name, req.specs)
    from pip._internal.req import parse_requirements
    from pip._internal.network.session import PipSession
    declared_deps = []
    for req in parse_requirements(fpath, session=PipSession()):
        declared_deps.append(req.requirement)
    return declared_deps


def parse_conda_reqs(fpath, blocklist=set()):
    import yaml
    with open(fpath, 'r') as file:
        data = yaml.load(file, Loader=yaml.SafeLoader)
    context = []
    found = []
    for dep in data['dependencies']:
        if isinstance(dep, dict) and 'pip' in dep:
            for line in dep['pip']:
                if line.startswith('-r '):
                    req_file = line.split(' ')[-1].replace('file:', '')
                    found.extend(parse_requirement_file(req_file))
                elif line.startswith('--'):
                    context.append(line)
                else:
                    name = line.split(' ')[0]
                    if name not in blocklist:
                        found.append(' '.join([line] + context))
    return found


# def query_pypi_dependencies(package_name, version=None):
#     """
#     Args:
#         >>> package_name = 'sympy'
#         >>> version = None
#     """
#     import requests
#     if version is not None:
#         url = f'https://pypi.python.org/pypi/{package_name}/{version}/json'
#     else:
#         url = f'https://pypi.python.org/pypi/{package_name}/json'
#     resp = requests.get(url)
#     assert resp.status_code == 200
#     depinfo = resp.json()
#     print('depinfo = {}'.format(ub.urepr(depinfo, nl=-1)))


# def pip_explore_deps(package_name, version=None):
#     cache_dpath = ub.ensure_app_cache_dir('watch/pypi_dep_resolve')
#     cmd = f'pip download -r requirements.txt -d {cache_dpath}'
#     print(cmd)
#     pass


def make_upgrade_strict_line():
    """
    import sys, ubelt
    sys.path.append(ubelt.expandpath('~/code/watch/dev'))
    from make_reqs_from_conda import *  # NOQA
    """
    defined_req_lines = parse_conda_reqs('conda_env.yml')

    def normalize_name(name):
        return name.lower().replace('-', '_')

    name_to_conda_line = {
        normalize_name(line.split(' ')[0].split('>')[0]): line
        for line in defined_req_lines
    }
    declared_deps = list(name_to_conda_line.keys())
    deps = list(ub.oset(declared_deps) - {'gdal', 'opencv-python-headless'})

    print('pip install ' + ' '.join(deps) + ' -U')


def trace_all_deps(defined_req_lines):
    r"""
    TODO: make this work.

    The issue is that the packages dependencies need to be installed for this
    to correctly find the dependencies.

    pip install requirements-parser

    Ignore:
        import sys, ubelt
        sys.path.append(ubelt.expandpath('~/code/watch/dev'))
        from make_reqs_from_conda import *  # NOQA
        defined_req_lines = parse_conda_reqs('conda_env.yml')
        groups = trace_all_deps(defined_req_lines)

        new = {}
        for k in ['Defined', 'Implied']:
            nover_lines = []
            for line in groups[k]:
                nover = line.partition('>')[0].partition('=')[0].partition(' ')[0]
                nover_lines.append(nover)
            new[k] = nover_lines

        for k in ['Defined', 'Implied']:
            print(r'\begin{multicols}{4}')
            print(r'\begin{itemize}')
            for nover in new[k]:
                print(r'    \item ' + nover.replace('_', '\_'))
            print(r'\end{itemize}')
            print(r'\end{multicols}')

        new['Implied'] = ub.oset(new['Implied']) - new['Defined']

        print('\begin{multicols}{3}')
        print('\begin{itemize}')
        for line in new['Implied']:
            nover = line.partition('>')[0].partition('=')[0].partition(' ')[0]
            print('    \item ' + nover.replace('_', '\_'))
        print('\end{itemize}')
        print('\end{multicols}')

    """
    import pipdeptree
    from distutils.version import LooseVersion

    def normalize_name(name):
        return name.lower().replace('-', '_')

    name_to_conda_line = {
        normalize_name(line.split(' ')[0]): line
        for line in defined_req_lines
    }
    declared_deps = list(name_to_conda_line.keys())

    pkgs = pipdeptree.get_installed_distributions()
    tree = pipdeptree.PackageDAG.from_pkgs(pkgs)
    tree = tree.filter(declared_deps, [])

    # for pkg, deps in tree.items():
    #     pkg_label = '{0} >= {1}'.format(pkg.project_name, pkg.version)
    #     print(pkg_label)

    frozen = False
    list_all = True

    tree = tree.sort()
    nodes = tree.keys()
    branch_keys = set(r.key for r in pipdeptree.flatten(tree.values()))
    use_bullets = not frozen

    if not list_all:
        nodes = [p for p in nodes if p.key not in branch_keys]

    def aux(node, parent=None, indent=0, chain=None):
        chain = chain or []
        node_str = node.render(parent, frozen)
        if parent:
            prefix = ' ' * indent + ('- ' if use_bullets else '')
            node_str = prefix + node_str

        node_dict = node.as_dict()
        yield node_dict
        for c in tree.get_children(node.key):
            if c.project_name not in chain:
                yield from aux(c, node, indent=indent + 2, chain=chain + [c.project_name])

    items = list(pipdeptree.flatten([aux(p) for p in nodes]))
    item_variants = ub.group_items(items, key=lambda d: d['key'])

    blocklist = {
        'opencv-python',
    }

    resolved = []
    for pkg_name, variants in item_variants.items():
        variants = list(ub.unique(variants, key=ub.hash_data))
        if len(variants) == 1:
            variant = variants[0]
        else:
            variant = max(variants, key=lambda x: (LooseVersion(x['installed_version']), 'required_version' in x))
            # print('RESOLVE pkg_name = {!r}'.format(pkg_name))
            # for variant in variants:
            #     print('variant = {!r}'.format(variant))
        if variant['key'] not in blocklist:
            resolved.append(variant)

    key_to_newline = {}
    for item in resolved:
        key = normalize_name(item['package_name'])
        if key in defined_req_lines:
            line = defined_req_lines[key]
        else:
            line = key + '>=' + item['installed_version']
        key_to_newline[key] = line

    toplevel = list(name_to_conda_line.values())
    remain = list(ub.dict_diff(key_to_newline, ub.oset(name_to_conda_line)).values())

    groups = {}
    groups['Defined'] = toplevel
    groups['Implied'] = remain
    return groups


def main():
    """
    Export pip requirements in a conda env file to a requirements.txt file
    """
    fpath = 'conda_env.yml'

    # There are issues with netharn and imgaug due to opencv-python
    # So keep them out of the direct dependencies
    blocklist = {
        'netharn',
        'imgaug',
    }
    defined_req_lines = parse_conda_reqs(fpath, blocklist=blocklist)
    header1 = ub.codeblock(
        '''
        # This file is autogenerated from conda_env.yml and does not include
        # netharn or imgaug due to their forced dependency on opencv-python
        # See all-explicit.txt for how to install these safely
        ''')
    text = '\n'.join([header1] + defined_req_lines)
    with open('requirements/autogen/all-implicit.txt', 'w') as file:
        file.write(text)
    print(text)

    header2 = ub.codeblock(
        '''
        # This is semi-autogenerated such that a working environment should be
        # able to be installed via
        # `pip install -r requirements/autogen/all-explicit.txt --no-deps`
        # which might help in avoiding opencv issues
        ''')
    groups = trace_all_deps(defined_req_lines)
    new_lines = ['# Defined'] + groups['Defined'] + ['', '# Implied'] + groups['Implied']
    new_lines = [header2, ''] + new_lines
    new_text = ('\n'.join(new_lines))
    with open('requirements/autogen/all-explicit.txt', 'w') as file:
        file.write(new_text)
    print(new_text)


def compare_strict_versions():
    """
    Test what installed versions are different from the strict versions
    """
    from distutils.version import LooseVersion
    fpath = 'conda_env.yml'
    blocklist = {
        'netharn',
        'imgaug',
    }
    defined_req_lines = parse_conda_reqs(fpath, blocklist=blocklist)

    defined_versions = {}
    for line in defined_req_lines:
        if '>=' in line:
            name, ver = line.split('>=')
            name = name.strip().replace('-', '_')
            defined_versions[name] = LooseVersion(ver.strip().split(' ')[0])

    import pipdeptree
    pkgs = pipdeptree.get_installed_distributions()

    have_versions = {}
    for pkg in pkgs:
        name = pkg.project_name.replace('-', '_')
        have_versions[name] = LooseVersion(pkg.version)

    common = set(have_versions) & set(defined_versions)
    for key in common:
        have_v = have_versions[key]
        def_v = defined_versions[key]
        if have_v != def_v:
            print(f'Diff: {key:<30}, have={str(have_v):<10} != defined={str(def_v):<10}')


if __name__ == '__main__':
    """

    pip install pipdeptree
    python ~/code/watch/dev/make_reqs_from_conda.py


    pipdeptree -p ubelt

    pip install -r requirements.txt

    pip uninstall opencv-python opencv-python-headless  && pip install opencv-python-headless

    --no-deps
    # pip download -r requirements.txt -d ./tmp-deps --no-binary :all: -v
    """
    main()
