"""Implementation of sanity rule."""
from __future__ import annotations

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_added = "v6.14.0"

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

    allowed_ignores_v2_9 = [
        "validate-modules:deprecation-mismatch",  # Note: 2.9 expects a deprecated key in the METADATA. It was removed in later versions.
        "validate-modules:invalid-documentation",  # Note: The removed_at_date key in the deprecated section is invalid for 2.9.
    ]

    allowed_ignores_all = [
        "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",
    ]
    _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 = ""

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

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

            ignores = self.allowed_ignores_all

            # If there is a ignore-2.9.txt file, add the v2_9 list of allowed ignores
            if "ignore-2.9.txt" in str(file.abspath):
                ignores = self.allowed_ignores_all + self.allowed_ignores_v2_9

            for line_num, entry in enumerate(entries, 1):
                if entry and entry[0] != "#":
                    try:
                        if "#" in entry:
                            entry, _ = entry.split("#")
                        (_, test) = entry.split()
                        if test not in 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

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

    @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
