import random
from typing import cast

import pytest

from debputy.packager_provided_files import detect_all_packager_provided_files
from debputy.path_matcher import ExactFileSystemPath
from debputy.plugin.api import DebputyPluginInitializer
from debputy.plugin.api.impl import PluginProvidedFeatureSet
from debputy.plugin.api.test_api import (
    initialize_plugin_under_test_preloaded,
    InitializedPluginUnderTest,
)
from debputy.substitution import NULL_SUBSTITUTION
from .util import FakeDirEntry, faked_binary_package, binary_package_table


def ppf_test_plugin(api: DebputyPluginInitializer) -> None:
    api.packager_provided_file(
        "arch-specific-dash",
        "./some/test-directory",
        allow_architecture_segment=True,
        filename_format="{name}.conf",
    )
    api.packager_provided_file(
        "arch.specific.dot",
        "./some/test.directory",
        allow_architecture_segment=True,
    )

    api.packager_provided_file(
        "arch.specific.with.priority",
        "./some/test.priority.directory",
        allow_architecture_segment=True,
        default_priority=60,
    )

    api.packager_provided_file(
        "packageless-fallback",
        "./some/test-plfb",
        packageless_is_fallback_for_all_packages=True,
    )
    api.packager_provided_file(
        "packageless.fallback",
        "./some/test.plfb",
        packageless_is_fallback_for_all_packages=True,
    )


@pytest.mark.parametrize(
    "package_name,basename,install_target,is_main_binary",
    [
        ("foo", "foo.arch-specific-dash", "./some/test-directory/foo.conf", True),
        # main package match
        ("foo", "arch-specific-dash", "./some/test-directory/foo.conf", True),
        # arch match
        ("foo", "foo.arch-specific-dash.amd64", "./some/test-directory/foo.conf", True),
        # Matches with periods in both package name and in the file type
        ("foo.bar", "foo.bar.arch.specific.dot", "./some/test.directory/foo.bar", True),
        ("foo.bar", "arch.specific.dot", "./some/test.directory/foo.bar", True),
        (
            "foo.bar",
            "foo.bar.arch.specific.dot.amd64",
            "./some/test.directory/foo.bar",
            True,
        ),
        # Priority
        (
            "foo.bar",
            "foo.bar.arch.specific.with.priority",
            "./some/test.priority.directory/60-foo.bar",
            True,
        ),
        (
            "foo.bar",
            "arch.specific.with.priority",
            "./some/test.priority.directory/60-foo.bar",
            True,
        ),
        (
            "foo.bar",
            "foo.bar.arch.specific.with.priority.amd64",
            "./some/test.priority.directory/60-foo.bar",
            True,
        ),
        # Name
        (
            "foo.bar",
            "foo.bar.special.name.arch.specific.with.priority",
            "./some/test.priority.directory/60-special.name",
            True,
        ),
        (
            "foo.bar",
            "foo.bar.special.name.arch.specific.with.priority.amd64",
            "./some/test.priority.directory/60-special.name",
            True,
        ),
        (
            "foo.bar",
            "packageless-fallback",
            "./some/test-plfb/foo.bar",
            False,
        ),
        (
            "foo.bar",
            "packageless.fallback",
            "./some/test.plfb/foo.bar",
            False,
        ),
    ],
)
def test_packager_provided_files(
    package_name: str, basename: str, install_target: str, is_main_binary: bool
) -> None:
    # Inject our custom files
    plugin = initialize_plugin_under_test_preloaded(
        1,
        ppf_test_plugin,
        plugin_name="pff-test-plugin",
    )
    debputy_plugin_feature_set = _fetch_debputy_plugin_feature_set(plugin)
    dir_entry = FakeDirEntry(basename, directory="debian")
    binary_under_test = faked_binary_package(
        package_name, is_main_package=is_main_binary
    )
    main_package = (
        binary_under_test if is_main_binary else faked_binary_package("main-pkg")
    )
    binaries = [main_package]
    if not is_main_binary:
        binaries.append(binary_under_test)
    binary_packages = binary_package_table(*binaries)
    ppf = detect_all_packager_provided_files(
        debputy_plugin_feature_set.packager_provided_files,
        [dir_entry],
        binary_packages,
        lambda n: NULL_SUBSTITUTION,
    )
    assert package_name in ppf
    all_matched = ppf[package_name]
    assert len(all_matched) == 1
    matched = all_matched[0]
    assert matched.source_path == dir_entry.path
    assert f"debian/{basename}" in matched.definition_source
    match_rule = matched.match_rule
    assert isinstance(match_rule, ExactFileSystemPath)
    assert match_rule.path == install_target


@pytest.mark.parametrize(
    "package_name,expected_basename,non_matched",
    [
        ("foo", "foo.arch-specific-dash", ["arch-specific-dash"]),
        (
            "foo",
            "foo.arch-specific-dash.amd64",
            [
                "foo.arch-specific-dash",
                "arch-specific-dash",
                "foo.arch-specific-dash.i386",
            ],
        ),
        (
            "foo",
            "foo.arch-specific-dash",
            ["arch-specific-dash", "foo.arch-specific-dash.i386"],
        ),
    ],
)
def test_packager_provided_files_priority(
    package_name, expected_basename, non_matched
) -> None:
    assert len(non_matched) > 0
    # Inject our custom files
    plugin = initialize_plugin_under_test_preloaded(
        1,
        ppf_test_plugin,
        plugin_name="pff-test-plugin",
    )
    debputy_plugin_feature_set = _fetch_debputy_plugin_feature_set(plugin)
    dir_entry = FakeDirEntry(expected_basename, directory="debian")
    binary_packages = binary_package_table(faked_binary_package(package_name))
    all_entries_base = [FakeDirEntry(x, directory="debian") for x in non_matched]
    for order in (0, len(all_entries_base), None):
        all_entries = all_entries_base.copy()
        print(f"Order: {order}")
        if order is not None:
            all_entries.insert(order, dir_entry)
        else:
            all_entries.append(dir_entry)
            # Ensure there are no order dependencies in the test by randomizing it.
            random.shuffle(all_entries)
        ppf = detect_all_packager_provided_files(
            debputy_plugin_feature_set.packager_provided_files,
            all_entries,
            binary_packages,
            lambda n: NULL_SUBSTITUTION,
        )
        assert package_name in ppf
        all_matched = ppf[package_name]
        assert len(all_matched) == 1
        matched = all_matched[0]
        assert matched.source_path == dir_entry.path
        assert f"debian/{expected_basename}" in matched.definition_source
        match_rule = matched.match_rule
        assert isinstance(match_rule, ExactFileSystemPath)


def _fetch_debputy_plugin_feature_set(
    plugin: InitializedPluginUnderTest,
) -> PluginProvidedFeatureSet:
    # Very much not public API, but we need it to avoid testing on production data (also, it is hard to find
    # relevant debputy files for all the cases we want to test).
    return cast("InitializedPluginUnderTestImpl", plugin)._feature_set
