from _pydevd_bundle.pydevd_constants import IS_WINDOWS


def test_in_project_roots_prefix_01(tmpdir):
    from _pydevd_bundle.pydevd_filtering import FilesFiltering
    files_filtering = FilesFiltering()

    another = str(tmpdir.join('another'))
    assert not another.endswith('/') and not another.endswith('\\')

    files_filtering.set_library_roots([another])
    files_filtering.set_project_roots([])
    assert not files_filtering.in_project_roots(another + '/f.py')
    if IS_WINDOWS:
        assert not files_filtering.in_project_roots(another + '\\f.py')
    else:
        assert files_filtering.in_project_roots(another + '\\f.py')

    assert files_filtering.in_project_roots(another + 'f.py')


def test_in_project_roots_prefix_02(tmpdir):
    from _pydevd_bundle.pydevd_filtering import FilesFiltering
    files_filtering = FilesFiltering()

    another = str(tmpdir.join('another'))
    assert not another.endswith('/') and not another.endswith('\\')

    files_filtering.set_library_roots([])
    files_filtering.set_project_roots([another])
    assert files_filtering.in_project_roots(another + '/f.py')
    if IS_WINDOWS:
        assert files_filtering.in_project_roots(another + '\\f.py')
    else:
        assert not files_filtering.in_project_roots(another + '\\f.py')

    assert not files_filtering.in_project_roots(another + 'f.py')


def test_in_project_roots(tmpdir):
    from _pydevd_bundle.pydevd_filtering import FilesFiltering
    files_filtering = FilesFiltering()

    import os.path
    import sys
    assert files_filtering._get_library_roots() == [
        os.path.normcase(x) + ('\\' if IS_WINDOWS else '/') for x in files_filtering._get_default_library_roots()]

    site_packages = tmpdir.mkdir('site-packages')
    project_dir = tmpdir.mkdir('project')

    project_dir_inside_site_packages = str(site_packages.mkdir('project'))
    site_packages_inside_project_dir = str(project_dir.mkdir('site-packages'))

    # Convert from pytest paths to str.
    site_packages = str(site_packages)
    project_dir = str(project_dir)
    tmpdir = str(tmpdir)

    # Test permutations of project dir inside site packages and vice-versa.
    files_filtering.set_project_roots([project_dir, project_dir_inside_site_packages])
    files_filtering.set_library_roots([site_packages, site_packages_inside_project_dir])

    check = [
        (tmpdir, False),
        (site_packages, False),
        (site_packages_inside_project_dir, False),
        (project_dir, True),
        (project_dir_inside_site_packages, True),
    ]
    for (check_path, find) in check[:]:
        filename_inside = os.path.join(check_path, 'a.py')
        with open(filename_inside, 'w') as stream:
            # Note: on Github actions, tmpdir may be something as:
            # c:\\users\\runner~1\\appdata\\local\\temp\\pytest-of-runneradmin\\pytest-0\\test_in_project_roots0
            # internally this may be set as:
            # c:\\users\\runneradmin\\appdata\\local\\temp\\pytest-of-runneradmin\\pytest-0\\test_in_project_roots0
            # So, when getting the absolute path, `runner~1` will be properly expanded to `runneradmin` if the
            # file exists, but if it doesn't it's not (which may make the test fail), so, make sure
            # that we actually create the file so that things work as expected.
            stream.write('...')
        check.append((filename_inside, find))

    for check_path, find in check:
        if files_filtering.in_project_roots(check_path) != find:
            if find:
                msg = 'Expected %s to be in the project roots.\nProject roots: %s\nLibrary roots: %s\n'
            else:
                msg = 'Expected %s NOT to be in the project roots.\nProject roots: %s\nLibrary roots: %s\n'

            raise AssertionError(msg % (
                check_path,
                files_filtering._get_project_roots(),
                files_filtering._get_library_roots(),
                )
            )

    files_filtering.set_project_roots([])
    files_filtering.set_library_roots([site_packages, site_packages_inside_project_dir])

    # If the IDE did not set the project roots, consider anything not in the site
    # packages as being in a project root (i.e.: we can calculate default values for
    # site-packages but not for project roots).
    check = [
        (tmpdir, True),
        (site_packages, False),
        (site_packages_inside_project_dir, False),
        (project_dir, True),
        (project_dir_inside_site_packages, False),
        ('<foo>', False),
        ('<ipython>', True),
        ('<frozen importlib._bootstrap>', False),
    ]

    for check_path, find in check:
        assert files_filtering.in_project_roots(check_path) == find, \
            'Expected: %s to be a part of the project: %s' % (check_path, find)

    sys.path.append(str(site_packages))
    try:
        default_library_roots = files_filtering._get_default_library_roots()
        assert len(set(default_library_roots)) == len(default_library_roots), \
            'Duplicated library roots found in: %s' % (default_library_roots,)

        assert str(site_packages) in default_library_roots
        for path in sys.path:
            if os.path.exists(path) and path.endswith('site-packages'):
                assert path in default_library_roots
    finally:
        sys.path.remove(str(site_packages))


def test_filtering(tmpdir):
    from _pydevd_bundle.pydevd_filtering import FilesFiltering
    from _pydevd_bundle.pydevd_filtering import ExcludeFilter
    files_filtering = FilesFiltering()

    site_packages = tmpdir.mkdir('site-packages')
    project_dir = tmpdir.mkdir('project')

    project_dir_inside_site_packages = str(site_packages.mkdir('project'))
    site_packages_inside_project_dir = str(project_dir.mkdir('site-packages'))

    files_filtering.set_exclude_filters([
        ExcludeFilter('**/project*', True, True),
        ExcludeFilter('**/bar*', False, True),
    ])
    assert files_filtering.exclude_by_filter('/foo/project', None) is True
    assert files_filtering.exclude_by_filter('/foo/unmatched', None) is None
    assert files_filtering.exclude_by_filter('/foo/bar', None) is False


def test_glob_matching():
    from _pydevd_bundle.pydevd_filtering import glob_matches_path

    # Linux
    for sep, altsep in (('\\', '/'), ('/', None)):

        def build(path):
            if sep == '/':
                return path
            else:
                return ('c:' + path).replace('/', '\\')

        assert glob_matches_path(build('/a'), r'*', sep, altsep)

        assert not glob_matches_path(build('/a/b/c/some.py'), '/a/**/c/so?.py', sep, altsep)

        assert glob_matches_path('/a/b/c', '/a/b/*')
        assert not glob_matches_path('/a/b', '/*')
        assert glob_matches_path('/a/b', '/*/b')
        assert glob_matches_path('/a/b', '**/*')
        assert not glob_matches_path('/a/b', '**/a')

        assert glob_matches_path(build('/a/b/c/d'), '**/d', sep, altsep)
        assert not glob_matches_path(build('/a/b/c/d'), '**/c', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '**/c/d', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '**/b/c/d', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '/*/b/*/d', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '**/c/*', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '/a/**/c/*', sep, altsep)

        # I.e. directories are expected to end with '/', so, it'll match
        # something as **/directory/**
        assert glob_matches_path(build('/a/b/c/'), '**/c/**', sep, altsep)
        assert glob_matches_path(build('/a/b/c/'), '**/c/', sep, altsep)
        # But not something as **/directory (that'd be a file match).
        assert not glob_matches_path(build('/a/b/c/'), '**/c', sep, altsep)
        assert not glob_matches_path(build('/a/b/c'), '**/c/', sep, altsep)

        assert glob_matches_path(build('/a/b/c/d.py'), '/a/**/c/*', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d.py'), '/a/**/c/*.py', sep, altsep)
        assert glob_matches_path(build('/a/b/c/some.py'), '/a/**/c/so*.py', sep, altsep)
        assert glob_matches_path(build('/a/b/c/some.py'), '/a/**/c/som?.py', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '/**', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d'), '/**/d', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d.py'), '/**/*.py', sep, altsep)
        assert glob_matches_path(build('/a/b/c/d.py'), '**/c/*.py', sep, altsep)

        if IS_WINDOWS:
            assert glob_matches_path(build('/a/b/c/d.py'), '**/C/*.py', sep, altsep)
            assert glob_matches_path(build('/a/b/C/d.py'), '**/c/*.py', sep, altsep)

        # Expected not to match.
        assert not glob_matches_path(build('/a/b/c/d'), '/**/d.py', sep, altsep)
        assert not glob_matches_path(build('/a/b/c/d.pyx'), '/a/**/c/*.py', sep, altsep)
        assert not glob_matches_path(build('/a/b/c/d'), '/*/d', sep, altsep)

        if sep == '/':
            assert not glob_matches_path(build('/a/b/c/d'), r'**\d', sep, altsep)  # Match with \ doesn't work on linux...
            assert not glob_matches_path(build('/a/b/c/d'), r'c:\**\d', sep, altsep)  # Match with drive doesn't work on linux...
        else:
            # Works in Windows.
            assert glob_matches_path(build('/a/b/c/d'), r'**\d', sep, altsep)
            assert glob_matches_path(build('/a/b/c/d'), r'c:\**\d', sep, altsep)

        # Corner cases
        assert not glob_matches_path(build('/'), r'', sep, altsep)
        assert glob_matches_path(build(''), r'', sep, altsep)
        assert not glob_matches_path(build(''), r'**', sep, altsep)
        assert glob_matches_path(build('/'), r'**', sep, altsep)
        assert glob_matches_path(build('/'), r'*', sep, altsep)


def test_rules_to_exclude_filter(tmpdir):
    from _pydevd_bundle.pydevd_process_net_command_json import _convert_rules_to_exclude_filters
    from _pydevd_bundle.pydevd_filtering import ExcludeFilter
    from random import shuffle
    dira = tmpdir.mkdir('a')
    dirb = dira.mkdir('b')
    fileb = dirb.join('fileb.py')
    fileb2 = dirb.join('fileb2.py')
    with fileb.open('w') as stream:
        stream.write('')

    def on_error(msg):
        raise AssertionError(msg)

    rules = [
        {'path': str(dira), 'include': False},
        {'path': str(dirb), 'include': True},
        {'path': str(fileb), 'include': True},
        {'path': str(fileb2), 'include': True},
        {'path': '**/foo/*.py', 'include': True},
        {'module': 'bar', 'include': False},
        {'module': 'bar.foo', 'include': True},
    ]
    shuffle(rules)
    exclude_filters = _convert_rules_to_exclude_filters(rules, on_error)
    assert exclude_filters == [
        ExcludeFilter(name=str(fileb2), exclude=False, is_path=True),
        ExcludeFilter(name=str(fileb), exclude=False, is_path=True),
        ExcludeFilter(name=str(dirb) + '/**', exclude=False, is_path=True),
        ExcludeFilter(name=str(dira) + '/**', exclude=True, is_path=True),
        ExcludeFilter(name='**/foo/*.py', exclude=False, is_path=True),
        ExcludeFilter(name='bar.foo', exclude=False, is_path=False),
        ExcludeFilter(name='bar', exclude=True, is_path=False),
    ]
