from typing import Iterable, Union, Tuple

import pytest

from debputy.filesystem_scan import build_virtual_fs
from debputy.intermediate_manifest import PathType
from debputy.path_matcher import MatchRule, PathMatcher


DEFAULT_RULE = "<default-match-rule>"


def verifying_path_matcher(
    paths: Iterable[Union[Tuple[MatchRule, str], str]],
    default_value: str,
) -> PathMatcher:
    match_rules = [
        (
            (MatchRule.from_path_or_glob(pt, f"test def: {pt}"), pt)
            if isinstance(pt, str)
            else pt
        )
        for pt in paths
    ]
    for match_rule, name in sorted(
        match_rules, key=lambda x: x[0].sort_key(), reverse=True
    ):
        print(
            f" * MR: {name}, match_rule: {match_rule.rule_type.name}, SK: {match_rule.sort_key()}"
        )
    return PathMatcher.from_dict({mr: p for mr, p in match_rules}, default_value)


@pytest.mark.parametrize(
    "testname,patterns,paths,all_expected",
    [
        (
            "verify-empty-directory",
            [
                "foo/*.txt",
            ],
            [
                "./foo/bar/",
                "./foo/test.txt",  # We had a bug where this would not be matched by foo/*.txt
            ],
            {"./foo/test.txt": "foo/*.txt"},
        ),
    ],
)
def test_consistency(testname, patterns, paths, all_expected):
    pm = verifying_path_matcher(patterns, DEFAULT_RULE)
    fs = build_virtual_fs(paths)
    seen = set()
    for fs_path, match_result in pm.resolve_all(fs):
        expected = all_expected.get(fs_path.path)
        if expected is None:
            continue
        seen.add(fs_path.path)
        assert fs_path is not None and match_result == expected
    assert seen == all_expected.keys()


def test_path_matcher():
    pm = verifying_path_matcher(
        [
            "foo/*",
            "foo",
            "foo/*.txt",
            "foo/a*",
            "libfoo/lib*.so",
            "lib*.so",
            "**/lib*.a",
            "./zz*",
        ],
        DEFAULT_RULE,
    )
    paths = {
        "./": DEFAULT_RULE,
        "./bar": DEFAULT_RULE,
        "./foo/": "foo",
        "./foo/file.txt": "foo/*.txt",
        "./foo/abc": "foo/a*",
        "./foo/random": "foo/*",
        "./libfoo/": DEFAULT_RULE,
        "./libfoo/libfoo.a": "**/lib*.a",
        "./libfoo/libfoo.so": "libfoo/lib*.so",
        "./libbar.a": "**/lib*.a",
        "./libbar.so": "lib*.so",
    }
    fs = build_virtual_fs(paths)
    for path, rule_text in pm.resolve_all(fs):
        # The "path part" of the assertion is mostly there to make the assertion contain the path
        assert path.tar_path is not None and paths[path.tar_path] == rule_text


def test_path_matcher_recursive():
    pm = verifying_path_matcher(
        [
            (MatchRule.recursive_beneath_directory("foo", "test def"), "foo/**/*"),
            "foo/*",
            "foo",
            "foo/*.txt",
            "foo/a*",
            "foo/lib*.so",
            "foo/bar/*",
        ],
        DEFAULT_RULE,
    )
    paths = {
        "./": DEFAULT_RULE,
        "./foo/": "foo",
        "./foo/bar/": "foo/*",
        "./foo/bar/a.txt": "foo/bar/*",
        "./foo/bar/baz/": "foo/bar/*",
        "./foo/bar/baz/a.txt": "foo/**/*",
        "./foo/file.txt": "foo/*.txt",
        "./foo/frobber/": "foo/*",
        "./foo/frobber/a.txt": "foo/**/*",
        "./foo/abc": "foo/a*",
        "./foo/libfoo.so": "foo/lib*.so",
        "./foo/random": "foo/*",
    }
    fs = build_virtual_fs(paths)
    for path, rule_text in pm.resolve_all(fs):
        # The "path part" of the assertion is mostly there to make the assertion contain the path
        assert path.tar_path is not None and paths[path.tar_path] == rule_text


def test_path_matcher_special_match_rules():
    pm = verifying_path_matcher(
        [
            (MatchRule.recursive_beneath_directory("foo", "test def"), "foo/**/*"),
            (
                MatchRule.from_path_or_glob(
                    "foo/bar*", "test def", path_type=PathType.FILE
                ),
                "foo/bar* [files-only]",
            ),
            (MatchRule.from_path_or_glob("foo/bar*", "test def"), "foo/bar* [generic]"),
            (
                MatchRule.from_path_or_glob(
                    "foo/*", "test def", path_type=PathType.FILE
                ),
                "foo/* [files-only]",
            ),
            (MatchRule.from_path_or_glob("foo/*", "test def"), "foo/* [generic]"),
            "foo",
            "foo/*.txt",
            "foo/a*",
            "foo/lib*.so",
            "foo/bar/*",
        ],
        DEFAULT_RULE,
    )
    paths = {
        "./": DEFAULT_RULE,
        "./foo/": "foo",
        "./foo/bar/": "foo/bar* [generic]",
        "./foo/bar/a.txt": "foo/bar/*",
        "./foo/bar/baz/": "foo/bar/*",
        "./foo/bar/baz/a.txt": "foo/**/*",
        "./foo/barbarrosa.conf": "foo/bar* [files-only]",
        "./foo/barbarrosa.d/": "foo/bar* [generic]",
        "./foo/file.txt": "foo/*.txt",
        "./foo/frobber/": "foo/* [generic]",
        "./foo/frobber/a.txt": "foo/**/*",
        "./foo/abc": "foo/a*",
        "./foo/libfoo.so": "foo/lib*.so",
        "./foo/random": "foo/* [files-only]",
    }
    fs = build_virtual_fs(paths)
    for path, rule_text in pm.resolve_all(fs):
        # The "path part" of the assertion is mostly there to make the assertion contain the path
        assert path.tar_path is not None and paths[path.tar_path] == rule_text


def test_path_matcher_generic_globs():
    pm = verifying_path_matcher(
        [
            "usr/share/d*/*/copyright",
            "usr/share/doc/sudo*/README",
            "usr/share/doc/sudo*/*.gz",
        ],
        DEFAULT_RULE,
    )
    paths = {
        "./usr/share/doc/sudo/copyright": "usr/share/d*/*/copyright",
        "./usr/share/doc/sudo/README": "usr/share/doc/sudo*/README",
        "./usr/share/doc/sudo/README.gz": "usr/share/doc/sudo*/*.gz",
        "./usr/share/doc/bar/data.gz": DEFAULT_RULE,
        "./usr/share/doc/sudo/examples/data.gz": DEFAULT_RULE,
        "./usr/share/doc/sudo-ldap/README": "usr/share/doc/sudo*/README",
        "./usr/share/doc/sudo-ldap/copyright": "usr/share/d*/*/copyright",
    }
    fs = build_virtual_fs(paths)
    matched = set()
    for path, rule_text in pm.resolve_all(fs):
        if path.tar_path not in paths:
            continue
        # The "path part" of the assertion is mostly there to make the assertion contain the path
        assert path.tar_path is not None and paths[path.tar_path] == rule_text
        matched.add(path.tar_path)
    assert matched == paths.keys()
