from typing import Iterable

from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable
from debputy.filesystem_scan import FSPath
from debputy.intermediate_manifest import PathType
from debputy.manifest_path_rules import ManifestPathRule
from debputy.packages import BinaryPackage
from debputy.path_matcher import (
    MATCH_ANYTHING,
    MatchRule,
    ExactFileSystemPath,
    DirectoryBasedMatch,
    MatchRuleType,
    BasenameGlobMatch,
)
from debputy.substitution import Substitution
from debputy.util import _normalize_path, perl_module_dirs

# Imported from dh_fixperms
_PERMISSION_NORMALIZATION_SOURCE_DEFINITION = "permission normalization"
_STD_FILE_MODE = 0o644
_PATH_FILE_MODE = 0o755

DEFAULT_PATH_INFO = ManifestPathRule.ensure_metadata(
    _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
    MATCH_ANYTHING,
    uname="root",
    uid=0,
    gname="root",
    gid=0,
    # This mode is from dh_fixperms
    symbolic_mode="go=rX,u+rw,a-s",
    is_builtin_definition=True,
)


class _UsrShareDocMatchRule(DirectoryBasedMatch):
    def __init__(self) -> None:
        super().__init__(
            MatchRuleType.ANYTHING_BENEATH_DIR,
            _normalize_path("usr/share/doc", with_prefix=True),
            path_type=PathType.FILE,
        )

    def finditer(self, fs_root: "FSPath") -> Iterable["FSPath"]:
        doc_dir = fs_root.lookup(self._directory)
        if doc_dir is None:
            return
        for path_in_doc_dir in doc_dir.iterdir:
            yield path_in_doc_dir
            for subpath in path_in_doc_dir.iterdir:
                if subpath.name == "examples" and subpath.is_dir:
                    continue
                yield from subpath.all_paths()

    def describe_match_short(self) -> str:
        return f"All files beneath {self._directory}/ except .../<pkg>/examples"

    def describe_match_exact(self) -> str:
        return self.describe_match_short()

    def __hash__(self):
        return object.__hash__(self)

    def __eq__(self, other: object) -> bool:
        return self is other


USR_SHARE_DOC_MATCH_RULE = _UsrShareDocMatchRule()
del _UsrShareDocMatchRule


def builtin_path_metadata_rules(
    dpkg_architecture_variables: DpkgArchitectureBuildProcessValuesTable,
    dctrl_bin: BinaryPackage,
    substitution: Substitution,
) -> Iterable["ManifestPathRule"]:
    yield from (
        ManifestPathRule.ensure_metadata(
            _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            MatchRule.from_path_or_glob(
                x, _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, path_type=PathType.FILE
            ),
            mode=_STD_FILE_MODE,
            is_builtin_definition=True,
        )
        for x in (
            "*.so.*",
            "*.so",
            "*.la",
            "*.a",
            "*.js",
            "*.css",
            "*.scss",
            "*.sass",
            "*.jpeg",
            "*.jpg",
            "*.png",
            "*.gif",
            "*.cmxs",
            "*.node",
        )
    )

    yield from (
        ManifestPathRule.ensure_metadata(
            _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            MatchRule.recursive_beneath_directory(
                x, _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, path_type=PathType.FILE
            ),
            mode=_STD_FILE_MODE,
            is_builtin_definition=True,
        )
        for x in (
            "usr/share/man",
            "usr/include",
            "usr/share/applications",
            "usr/share/lintian/overrides",
        )
    )

    # The dh_fixperms tool recuses for these directories, but probably should not (see #1006927)
    yield from (
        ManifestPathRule.ensure_metadata(
            _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            MatchRule.from_path_or_glob(
                f"{x}/*",
                _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
                path_type=PathType.FILE,
            ),
            mode=_PATH_FILE_MODE,
            is_builtin_definition=True,
        )
        for x in (
            "usr/bin",
            "usr/bin/mh",
            "bin",
            "usr/sbin",
            "sbin",
            "usr/games",
            "usr/libexec",
            "etc/init.d",
        )
    )
    yield from (
        # Strictly speaking, dh_fixperms does a recursive search but in practice, it does not matter.
        ManifestPathRule.ensure_metadata(
            _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            MatchRule.from_path_or_glob(
                x, _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, path_type=PathType.FILE
            ),
            mode=0o440,
            is_builtin_definition=True,
        )
        for x in ("etc/sudoers.d/*",)
    )

    # The reportbug rule
    yield ManifestPathRule.ensure_metadata(
        _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
        ExactFileSystemPath(
            substitution.substitute(
                _normalize_path("usr/share/bug/{{PACKAGE}}"),
                _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            )
        ),
        mode=0o755,
        dirmode=0o755,
        is_builtin_definition=True,
    )
    yield ManifestPathRule.ensure_metadata(
        _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
        MatchRule.recursive_beneath_directory(
            "usr/share/bug/{{PACKAGE}}",
            _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            path_type=PathType.FILE,
            substitution=substitution,
        ),
        mode=0o644,
        is_builtin_definition=True,
    )
    yield ManifestPathRule.ensure_metadata(
        _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
        ExactFileSystemPath(
            substitution.substitute(
                _normalize_path("usr/share/bug/{{PACKAGE}}/script"),
                _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            )
        ),
        mode=0o755,
        dirmode=0o755,
        is_builtin_definition=True,
    )
    yield ManifestPathRule.ensure_metadata(
        _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
        USR_SHARE_DOC_MATCH_RULE,
        mode=0o0644,
        is_builtin_definition=True,
    )
    yield from (
        ManifestPathRule.ensure_metadata(
            _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
            BasenameGlobMatch(
                "*.pm",
                only_when_in_directory=perl_dir,
                path_type=PathType.FILE,
                recursive_match=True,
            ),
            symbolic_mode="a-x",
            is_builtin_definition=True,
        )
        for perl_dir in perl_module_dirs(dpkg_architecture_variables, dctrl_bin)
    )
    yield ManifestPathRule.ensure_metadata(
        _PERMISSION_NORMALIZATION_SOURCE_DEFINITION,
        BasenameGlobMatch(
            "*.ali",
            only_when_in_directory=_normalize_path("usr/lib"),
            path_type=PathType.FILE,
            recursive_match=True,
        ),
        symbolic_mode="a-w",
        is_builtin_definition=True,
    )
