"""Specialist: hardcoded secrets, API keys, passwords in source code."""

from __future__ import annotations

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


# Patterns that indicate a hardcoded secret
_SECRET_PATTERNS = [
    (re.compile(r'^["\'](?:sk-|pk_live_|pk_test_|rk_live_|rk_test_|sk_live_|sk_test_)'), "API key"),
    (re.compile(r'^["\'](?:ghp_|gho_|ghu_|ghs_|ghr_)'), "GitHub token"),
    (re.compile(r'^["\'](?:xox[bpsa]-|xapp-)'), "Slack token"),
    (re.compile(r'^["\']AKIA[0-9A-Z]{16}'), "AWS access key"),
    (re.compile(r'^["\']eyJ[a-zA-Z0-9_-]{20,}\.eyJ'), "JWT token"),
]

# Variable names that suggest secrets
_SECRET_NAMES = re.compile(
    r'(?:password|passwd|secret|api_key|apikey|access_token|auth_token|'
    r'private_key|secret_key|credentials?|connection_string)$',
    re.IGNORECASE,
)


class HardcodedSecretsSpecialist(Specialist):
    name = "hardcoded_secrets"
    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):
            if not isinstance(node, ast.Assign):
                continue

            # Get the variable name
            if not node.targets:
                continue
            target = node.targets[0]
            var_name = None
            if isinstance(target, ast.Name):
                var_name = target.id
            elif isinstance(target, ast.Attribute):
                var_name = target.attr
            if not var_name:
                continue

            # Check if value is a non-empty string constant
            if not isinstance(node.value, ast.Constant):
                continue
            if not isinstance(node.value.value, str):
                continue
            value = node.value.value
            if len(value) < 8:
                continue  # too short to be a real secret

            # Skip obvious non-secrets
            if value.startswith(("http://", "https://", "/", ".", "~")):
                continue
            if value in ("", "None", "null", "undefined", "TODO", "CHANGEME", "placeholder"):
                continue

            # Check for known token patterns
            for pattern, secret_type in _SECRET_PATTERNS:
                if pattern.search(repr(value)):
                    findings.append(Finding(
                        line=node.lineno,
                        severity="HIGH",
                        message=f"Hardcoded {secret_type} in `{var_name}` — use environment variable or secrets manager",
                        specialist=self.name,
                        confidence="DEFINITE",
                    ))
                    break
            else:
                # Check if variable name suggests a secret
                if _SECRET_NAMES.search(var_name):
                    # Skip if value looks like a placeholder
                    if any(p in value.lower() for p in ("xxx", "changeme", "placeholder", "example", "test", "dummy", "fake")):
                        continue
                    # Skip if it's os.environ.get or similar
                    findings.append(Finding(
                        line=node.lineno,
                        severity="HIGH",
                        message=f"Possible hardcoded secret in `{var_name}` — use environment variable",
                        specialist=self.name,
                        confidence="LIKELY",
                    ))

        return findings
