"""Specialist: mutable default arguments, eval/exec on non-literals, bare assert."""

from __future__ import annotations

import ast
from autodebug.agent.specialists.base import ASTData, Finding, Specialist


class UnsafeDefaultsSpecialist(Specialist):
    name = "unsafe_defaults"
    trigger_features: list[str] = []  # always run

    async def run(self, source: str, ast_data: ASTData) -> list[Finding]:
        findings: list[Finding] = []

        for node in ast.walk(ast_data.tree):
            # ── Mutable default arguments ──
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                for default in node.args.defaults + node.args.kw_defaults:
                    if default is None:
                        continue
                    if isinstance(default, (ast.List, ast.Dict, ast.Set)):
                        findings.append(Finding(
                            line=default.lineno,
                            severity="MEDIUM",
                            message=f"Mutable default argument in `{node.name}()` — shared across calls, causes subtle bugs",
                            specialist=self.name,
                            confidence="DEFINITE",
                        ))
                        break  # one per function is enough

            # ── eval()/exec() on non-literal ──
            elif isinstance(node, ast.Call):
                if isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
                    if node.args:
                        arg = node.args[0]
                        # Literal string eval is intentional (e.g. ast.literal_eval pattern)
                        if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
                            continue
                        findings.append(Finding(
                            line=node.lineno,
                            severity="HIGH",
                            message=f"`{node.func.id}()` on dynamic input — arbitrary code execution risk",
                            specialist=self.name,
                            confidence="DEFINITE",
                        ))

            # ── Bare assert in non-test code ──
            # Removed: too noisy. Almost nobody runs python -O.
            # assert is idiomatic Python for preconditions.

        return findings
