"""Checker for dangerous eval() usage."""

import re
from pathlib import Path
from typing import List

from stablestack.checkers.base import BaseChecker
from stablestack.models import Finding, Severity, Tier


class EvalUsageChecker(BaseChecker):
    """Detects eval() and similar dangerous code execution functions."""

    rule_id = "SEC003"
    rule_name = "eval-usage"
    description = "eval() executes arbitrary code and is a security risk"
    why = (
        "eval() takes a string and executes it as code. If any part of that string "
        "comes from user input, attackers can run any code they want on your system. "
        "There's almost always a safer alternative."
    )
    severity = Severity.ERROR
    tier = Tier.CRITICAL  # Security issues are always critical
    file_extensions = {".py", ".js", ".ts", ".jsx", ".tsx", ".rb"}

    # Dangerous functions
    _PY_PATTERNS = [
        (re.compile(r'\beval\s*\('), 'eval()'),
        (re.compile(r'\bexec\s*\('), 'exec()'),
        # compile() is dangerous when building code dynamically, but not re.compile()
        (re.compile(r'(?<!re\.)\bcompile\s*\([^)]+[\'"][^)]+[\'"]'), 'compile()'),
        (re.compile(r'__import__\s*\('), '__import__()'),
    ]

    _JS_PATTERNS = [
        (re.compile(r'\beval\s*\('), 'eval()'),
        (re.compile(r'\bFunction\s*\('), 'new Function()'),
        (re.compile(r'setTimeout\s*\(\s*[\'"`]'), 'setTimeout with string'),
        (re.compile(r'setInterval\s*\(\s*[\'"`]'), 'setInterval with string'),
    ]

    def check_file(self, path: Path, content: str) -> List[Finding]:
        findings = []
        lines = content.splitlines()

        # Build a set of lines that are in docstrings or strings
        in_docstring = False
        docstring_lines = set()
        for i, line in enumerate(lines):
            stripped = line.lstrip()
            triple_quotes = stripped.count('"""') + stripped.count("'''")
            if triple_quotes == 1:
                in_docstring = not in_docstring
                docstring_lines.add(i)
            elif triple_quotes >= 2:
                docstring_lines.add(i)  # Single-line docstring
            if in_docstring:
                docstring_lines.add(i)

        patterns = self._PY_PATTERNS if path.suffix == '.py' else self._JS_PATTERNS

        for pattern, func_name in patterns:
            for match in pattern.finditer(content):
                line_num = content[:match.start()].count('\n') + 1
                line_idx = line_num - 1
                line_content = lines[line_idx] if line_idx < len(lines) else ""

                # Skip if in docstring
                if line_idx in docstring_lines:
                    continue

                # Skip if it's in a comment
                stripped = line_content.lstrip()
                if stripped.startswith('#') or stripped.startswith('//'):
                    continue

                # Skip if the match is inside a string literal (common for fix hints)
                if '= "' in line_content or "= '" in line_content:
                    continue

                findings.append(
                    self.create_finding(
                        path=path,
                        line=line_num,
                        column=match.start() - content.rfind('\n', 0, match.start()),
                        code=line_content.strip(),
                        message=(
                            f"Dangerous use of {func_name}. "
                            "This can execute arbitrary code if input is not trusted."
                        ),
                        fix_hint=self._get_fix_hint(func_name, path.suffix),
                    )
                )

        return findings

    def _get_fix_hint(self, func_name: str, suffix: str) -> str:
        """Get a contextual fix hint based on the function and language."""
        if func_name == 'eval()':
            if suffix == '.py':
                return (
                    "Instead of eval(), consider:\n"
                    "- ast.literal_eval() for parsing literals safely\n"
                    "- json.loads() for JSON data\n"
                    "- A proper parser for expressions\n"
                    "- A restricted evaluation library like simpleeval"
                )
            else:
                return (
                    "Instead of eval(), consider:\n"
                    "- JSON.parse() for JSON data\n"
                    "- A proper expression parser\n"
                    "- Restructuring to avoid dynamic code execution"
                )
        elif 'Function' in func_name:
            return (
                "new Function() is as dangerous as eval().\n"
                "Consider using a proper parser or restructuring your code."
            )
        elif 'setTimeout' in func_name or 'setInterval' in func_name:
            return (
                "Passing strings to setTimeout/setInterval uses eval internally.\n"
                "Pass a function instead:\n"
                "setTimeout(() => doSomething(), 1000)"
            )
        else:
            return "Avoid executing arbitrary code. Use safer alternatives."
