"""Specialist: missing None checks — .get() result used without guard, unchecked optional returns."""

from __future__ import annotations

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


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

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

        parent_map: dict[int, ast.AST] = {}
        for node in ast.walk(ast_data.tree):
            for child in ast.iter_child_nodes(node):
                parent_map[id(child)] = node

        for node in ast.walk(ast_data.tree):
            if not isinstance(node, ast.Call):
                continue

            # Pattern: x.get("key").something — .get() can return None
            if (isinstance(node.func, ast.Attribute)
                    and node.func.attr == "get"
                    and len(node.args) >= 1
                    and len(node.args) <= 1):  # no default provided

                parent = parent_map.get(id(node))
                # Check if the .get() result is immediately accessed
                if isinstance(parent, ast.Attribute):
                    # x.get("key").attr — will crash if None
                    findings.append(Finding(
                        line=node.lineno,
                        severity="MEDIUM",
                        message=f"`.get()` without default chained with `.{parent.attr}` — crashes with `AttributeError` if key missing",
                        specialist=self.name,
                        confidence="DEFINITE",
                    ))
                elif isinstance(parent, ast.Subscript):
                    # x.get("key")[0] — will crash if None
                    findings.append(Finding(
                        line=node.lineno,
                        severity="MEDIUM",
                        message="`.get()` without default used with subscript — crashes with `TypeError` if key missing",
                        specialist=self.name,
                        confidence="DEFINITE",
                    ))

            # Pattern: == None / != None instead of is None / is not None
            if isinstance(node, ast.Compare):
                pass  # handled below

        # Separate walk for Compare nodes
        for node in ast.walk(ast_data.tree):
            if not isinstance(node, ast.Compare):
                continue
            for op, comparator in zip(node.ops, node.comparators):
                if isinstance(comparator, ast.Constant) and comparator.value is None:
                    if isinstance(op, ast.Eq):
                        findings.append(Finding(
                            line=node.lineno,
                            severity="LOW",
                            message="`== None` should be `is None` (identity check, not equality)",
                            specialist=self.name,
                            confidence="DEFINITE",
                        ))
                    elif isinstance(op, ast.NotEq):
                        findings.append(Finding(
                            line=node.lineno,
                            severity="LOW",
                            message="`!= None` should be `is not None` (identity check, not equality)",
                            specialist=self.name,
                            confidence="DEFINITE",
                        ))

        return findings
