"""GO-SEC-001: Hardcoded Secrets detection for Go.

Detects hardcoded passwords, tokens, API keys, and secrets
in Go source code assignments and variable declarations.
"""

from __future__ import annotations

import re

from . import GoRule
from ..models import Finding, Severity

# Variable assignment patterns for secrets
# Matches: password := "...", var token = "...", apiKey = "...", etc.
SECRET_VAR_PATTERN = re.compile(
    r'\b(?:password|passwd|pwd|token|apikey|api_key|apiKey|secret|'
    r'secretkey|secret_key|secretKey|accesskey|access_key|accessKey|'
    r'privatekey|private_key|privateKey|authtoken|auth_token|authToken|'
    r'credentials|bearer)\s*(?::=|=)\s*["`]([^"`]{4,})["`]',
    re.IGNORECASE,
)

# Const block with secrets
SECRET_CONST_PATTERN = re.compile(
    r'(?:password|passwd|pwd|token|apikey|api_key|apiKey|secret|'
    r'secretkey|secret_key|secretKey|accesskey|access_key|accessKey|'
    r'privatekey|private_key|privateKey|authtoken|auth_token|authToken|'
    r'credentials|bearer)\s*(?:string)?\s*=\s*["`]([^"`]{4,})["`]',
    re.IGNORECASE,
)

# Connection strings with embedded credentials
CONN_STRING_PATTERN = re.compile(
    r'["`](?:postgres|mysql|mongodb|redis|amqp)://\w+:[^@]+@',
    re.IGNORECASE,
)

# Authorization header hardcoded
AUTH_HEADER_PATTERN = re.compile(
    r'["`](?:Authorization|Bearer|Basic)\s+[^"`]{8,}["`]',
)


class GoHardcodedSecretsRule(GoRule):
    """Detects hardcoded secrets in Go source code."""

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

    @property
    def name(self) -> str:
        return "Hardcoded Secrets"

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

    @property
    def cwe(self) -> str:
        return "CWE-798"

    @property
    def description(self) -> str:
        return (
            "Hardcoded passwords, tokens, or API keys found in Go source code."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "Hardcoded passwords, tokens, or API keys found in Go source code.\n\n"
            "Secrets embedded in source code are exposed in version control,\n"
            "build artifacts, and container images. Anyone with read access\n"
            "to the repository can extract them.\n\n"
            "Examples of vulnerable code:\n"
            '  password := "super_secret_123"\n'
            '  const apiKey = "sk-abc123..."\n'
            '  dsn := "postgres://admin:password@db:5432/app"\n\n'
            "How to fix:\n"
            "  - Use environment variables: os.Getenv(\"API_KEY\")\n"
            "  - Use a secrets manager (Vault, AWS Secrets Manager)\n"
            "  - Use Go's config file loading (viper, envconfig)"
        )

    def check(self, source: str, filename: str = "") -> list[Finding]:
        """Check for hardcoded secrets."""
        findings: list[Finding] = []

        # Skip test files for some patterns (test fixtures are OK)
        is_test = filename.endswith("_test.go")

        # Variable assignments with secret values
        for lineno, line, match in self._search_lines(source, SECRET_VAR_PATTERN):
            value = match.group(1) if match.lastindex else ""
            # Skip placeholder/example values
            if self._is_placeholder(value):
                continue
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=self.severity,
                title="Hardcoded Secret",
                description=(
                    f"Secret value assigned to variable (line {lineno})."
                ),
                file="",
                line=lineno,
                code_snippet=self._redact(line.strip()[:80]),
                recommendation=(
                    "Use os.Getenv() or a secrets manager instead of "
                    "hardcoding credentials."
                ),
                cwe=self.cwe,
            ))

        # Const declarations with secrets
        for lineno, line, match in self._search_lines(source, SECRET_CONST_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            value = match.group(1) if match.lastindex else ""
            if self._is_placeholder(value):
                continue
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=self.severity,
                title="Hardcoded Secret (const)",
                description=(
                    f"Secret in const declaration (line {lineno})."
                ),
                file="",
                line=lineno,
                code_snippet=self._redact(line.strip()[:80]),
                recommendation=(
                    "Move secrets to environment variables or config files."
                ),
                cwe=self.cwe,
            ))

        # Connection strings with embedded credentials
        if not is_test:
            for lineno, line, _match in self._search_lines(source, CONN_STRING_PATTERN):
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=Severity.CRITICAL,
                    title="Hardcoded Connection String",
                    description=(
                        "Database connection string with embedded credentials."
                    ),
                    file="",
                    line=lineno,
                    code_snippet=self._redact(line.strip()[:80]),
                    recommendation=(
                        "Use environment variables for connection strings."
                    ),
                    cwe=self.cwe,
                ))

        # Hardcoded Authorization headers
        for lineno, line, _match in self._search_lines(source, AUTH_HEADER_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.HIGH,
                title="Hardcoded Auth Header",
                description=(
                    "Authorization header with hardcoded token."
                ),
                file="",
                line=lineno,
                code_snippet=self._redact(line.strip()[:80]),
                recommendation=(
                    "Load auth tokens from environment or config."
                ),
                cwe=self.cwe,
            ))

        return findings

    def _is_placeholder(self, value: str) -> bool:
        """Check if a value is a placeholder/example."""
        placeholders = {
            "xxx", "XXX", "changeme", "CHANGEME", "todo", "TODO",
            "your_", "YOUR_", "example", "EXAMPLE", "placeholder",
            "test", "TEST", "dummy", "DUMMY", "fake", "FAKE",
            "replace", "REPLACE",
        }
        lower = value.lower()
        return any(p.lower() in lower for p in placeholders)

    def _redact(self, snippet: str) -> str:
        """Redact potential secret values in snippets."""
        # Replace quoted strings longer than 6 chars with [REDACTED]
        return re.sub(r'["`][^"`]{6,}["`]', '"[REDACTED]"', snippet)
