"""Optional Ansible-lint rule to enforce use of prefix on role loop vars."""

from __future__ import annotations

import re
import sys
from typing import TYPE_CHECKING

from ansiblelint.config import LOOP_VAR_PREFIX, options
from ansiblelint.rules import AnsibleLintRule
from ansiblelint.text import toidentifier

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


class RoleLoopVarPrefix(AnsibleLintRule):
    """Role loop_var should use configured prefix."""

    id = "loop-var-prefix"
    link = (
        "https://docs.ansible.com/ansible/latest/playbook_guide/"
        "playbooks_loops.html#defining-inner-and-outer-variable-names-with-loop-var"
    )
    description = """\
Looping inside roles has the risk of clashing with loops from user-playbooks.\
"""

    tags = ["idiom"]
    prefix = re.compile(r"")
    severity = "MEDIUM"
    _ids = {
        "loop-var-prefix[wrong]": "Loop variable name does not match regex.",
        "loop-var-prefix[missing]": "Replace unsafe implicit `item` loop variable.",
    }
    version_changed = "6.7.0"

    def matchtask(
        self,
        task: Task,
        file: Lintable | None = None,
    ) -> list[MatchError]:
        """Return matches for a task."""
        if not file or not file.role or not options.loop_var_prefix:
            return []

        self.prefix = re.compile(
            options.loop_var_prefix.format(role=toidentifier(file.role)),
        )
        has_loop = False
        if "loop" in task.raw_task:
            data = task.raw_task["loop"]
            has_loop = True
        else:
            for key in task.raw_task:
                if key.startswith("with_"):
                    data = key
                    has_loop = True
                    break

        if has_loop:
            loop_control = task.raw_task.get("loop_control", {})
            loop_var = loop_control.get("loop_var", "")

            if loop_var:
                if not self.prefix.match(loop_var):
                    return [
                        self.create_matcherror(
                            message=f"Loop variable name does not match /{options.loop_var_prefix}/ regex, where role={toidentifier(file.role)}.",
                            filename=file,
                            data=loop_var,
                            tag="loop-var-prefix[wrong]",
                        ),
                    ]
            else:
                return [
                    self.create_matcherror(
                        message=f"Replace unsafe implicit `item` loop variable by adding a `loop_var` that is matching /{options.loop_var_prefix}/ regex.",
                        filename=file,
                        data=data,
                        tag="loop-var-prefix[missing]",
                    ),
                ]

        return []


# 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"),
        (
            pytest.param(
                "examples/playbooks/roles/loop_var_prefix/tasks/pass.yml",
                0,
                id="pass",
            ),
            pytest.param(
                "examples/playbooks/roles/loop_var_prefix/tasks/fail.yml",
                6,
                id="fail",
            ),
        ),
    )
    def test_loop_var_prefix(
        default_rules_collection: RulesCollection,
        test_file: str,
        failures: int,
    ) -> None:
        """Test rule matches."""
        # Enable checking of loop variable prefixes in roles
        options.loop_var_prefix = LOOP_VAR_PREFIX
        results = Runner(test_file, rules=default_rules_collection).run()
        for result in results:
            assert result.rule.id == RoleLoopVarPrefix().id
        assert len(results) == failures
