"""Implementation of sanity rule."""

from __future__ import annotations

import re
import sys
from typing import TYPE_CHECKING

from ansiblelint.rules import AnsibleLintRule

# Copyright (c) 2018, Ansible Project


if TYPE_CHECKING:
    from ansiblelint.errors import MatchError
    from ansiblelint.file_utils import Lintable


class CheckSanityIgnoreFiles(AnsibleLintRule):
    """Ignore entries in sanity ignore files must match an allow list."""

    id = "sanity"
    description = (
        "Identifies non-allowed entries in the `tests/sanity/ignore*.txt files."
    )
    severity = "MEDIUM"
    tags = ["idiom"]
    version_changed = "6.14.0"

    # Partner Engineering defines this list. Please contact PE for changes.

    allowed_ignores = [
        "validate-modules:missing-gplv3-license",
        "action-plugin-docs",  # Added for Networking Collections
        "import-2.6",
        "import-2.6!skip",
        "import-2.7",
        "import-2.7!skip",
        "import-3.5",
        "import-3.5!skip",
        "compile-2.6",
        "compile-2.6!skip",
        "compile-2.7",
        "compile-2.7!skip",
        "compile-3.5",
        "compile-3.5!skip",
        "shebang",  # Unreliable test
        "shellcheck",  # Unreliable test
        "pylint:used-before-assignment",  # Unreliable test
    ]

    no_check_ignore_files = [
        "ignore-2.9",
        "ignore-2.10",
        "ignore-2.11",
        "ignore-2.12",
    ]

    _ids = {
        "sanity[cannot-ignore]": "Ignore file contains ... at line ..., which is not a permitted ignore.",
        "sanity[bad-ignore]": "Ignore file entry at ... is formatted incorrectly. Please review.",
    }

    def matchyaml(self, file: Lintable) -> list[MatchError]:
        """Evaluate sanity ignore lists for disallowed ignores.

        :param file: Input lintable file that is a match for `sanity-ignore-file`
        :returns: List of errors matched to the input file
        """
        results: list[MatchError] = []
        test = ""

        check_dirs = {
            "plugins",
            "roles",
        }

        if file.kind != "sanity-ignore-file":
            return []

        with file.path.open(encoding="utf-8") as ignore_file:
            entries = ignore_file.read().splitlines()

            if any(name in str(file.abspath) for name in self.no_check_ignore_files):
                return []

            for line_num, entry in enumerate(entries, 1):
                base_ignore_dir = ""

                if entry:
                    # match up to the first "/"
                    regex = re.match(r"[^/]*", entry)

                    if regex:
                        base_ignore_dir = regex.group(0)

                    if base_ignore_dir in check_dirs:
                        try:
                            if "#" in entry:
                                entry, _ = entry.split("#", 1)
                            (_, test) = entry.split()
                            if test not in self.allowed_ignores:
                                results.append(
                                    self.create_matcherror(
                                        message=f"Ignore file contains {test} at line {line_num}, which is not a permitted ignore.",
                                        tag="sanity[cannot-ignore]",
                                        lineno=line_num,
                                        filename=file,
                                    ),
                                )

                        except ValueError:
                            results.append(
                                self.create_matcherror(
                                    message=f"Ignore file entry at {line_num} is formatted incorrectly. Please review.",
                                    tag="sanity[bad-ignore]",
                                    lineno=line_num,
                                    filename=file,
                                ),
                            )

        return results


# testing code to be loaded only with pytest or when executed the rule file
if "pytest" in sys.modules:
    import pytest

    # pylint: disable=ungrouped-imports
    from ansiblelint.rules import RulesCollection
    from ansiblelint.runner import Runner

    @pytest.mark.parametrize(
        ("test_file", "failures", "tags"),
        (
            pytest.param(
                "examples/sanity_ignores/tests/sanity/ignore-2.9.txt",
                0,
                "sanity[cannot-ignore]",
                id="pass",
            ),
            pytest.param(
                "examples/sanity_ignores/tests/sanity/ignore-2.15.txt",
                1,
                "sanity[bad-ignore]",
                id="fail0",
            ),
            pytest.param(
                "examples/sanity_ignores/tests/sanity/ignore-2.13.txt",
                1,
                "sanity[cannot-ignore]",
                id="fail1",
            ),
        ),
    )
    def test_sanity_ignore_files(
        default_rules_collection: RulesCollection,
        test_file: str,
        failures: int,
        tags: str,
    ) -> None:
        """Test rule matches."""
        default_rules_collection.register(CheckSanityIgnoreFiles())
        results = Runner(test_file, rules=default_rules_collection).run()
        for result in results:
            assert result.rule.id == CheckSanityIgnoreFiles().id
            assert result.tag == tags
        assert len(results) == failures
