"""LOG-001: Sensitive Data in Logs detection rule."""

from __future__ import annotations

import ast
import re

from . import Rule
from ..models import Finding, Severity

LOG_FUNCS = {
    "logging.info", "logging.debug", "logging.warning", "logging.error",
    "logging.critical", "logging.exception",
    "logger.info", "logger.debug", "logger.warning", "logger.error",
    "logger.critical", "logger.exception",
    "log.info", "log.debug", "log.warning", "log.error",
}

SENSITIVE_PATTERN = re.compile(
    r"(secret|password|passwd|token|api_key|apikey|credential|private_key)",
    re.IGNORECASE,
)


class SensitiveLoggingRule(Rule):
    """Detects logging calls that may expose sensitive data."""

    @property
    def rule_id(self) -> str:
        return "LOG-001"

    @property
    def name(self) -> str:
        return "Sensitive Data in Logs"

    @property
    def severity(self) -> Severity:
        return Severity.MEDIUM

    @property
    def description(self) -> str:
        return "Logging calls that may expose secrets, tokens, or credentials."

    def _has_sensitive_name(self, node: ast.expr) -> bool:
        if isinstance(node, ast.Name):
            return bool(SENSITIVE_PATTERN.search(node.id))
        if isinstance(node, ast.JoinedStr):
            for val in node.values:
                if isinstance(val, ast.FormattedValue) and self._has_sensitive_name(val.value):
                    return True
        return False

    def check(self, tree: ast.Module, source: str) -> list[Finding]:
        findings: list[Finding] = []
        for node in ast.walk(tree):
            if not isinstance(node, ast.Call):
                continue
            func_name = self._get_func_name(node)
            is_log = func_name in LOG_FUNCS or func_name == "print"
            if not is_log:
                continue
            for arg in node.args:
                if self._has_sensitive_name(arg):
                    findings.append(Finding(
                        rule_id=self.rule_id,
                        severity=self.severity,
                        title="Sensitive data in log output",
                        description=f"{func_name}() may expose sensitive data.",
                        file="",
                        line=node.lineno,
                        code_snippet=self._get_snippet(source, node.lineno),
                        recommendation="Sanitize or redact sensitive data before logging.",
                        cwe="CWE-532",
                    ))
                    break
        return findings
