"""MCP Secrets Detector — Advanced secret detection with low false-positive rate.

Detects 30+ secret types including AWS keys, GCP service accounts, GitHub tokens,
Slack tokens, JWT tokens, private keys, database connection strings, and generic
API keys. Uses entropy-based detection for unknown secret patterns. Supports
configurable allowlists.

Zero external dependencies — pure Python 3.10+ stdlib.
"""

from __future__ import annotations

import math
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


# ---------------------------------------------------------------------------
# Data models
# ---------------------------------------------------------------------------

@dataclass
class DetectedSecret:
    """A single detected secret."""

    secret_type: str  # aws_key, gcp_service_account, github_token, etc.
    pattern_name: str
    value_masked: str
    file: str
    line: int
    entropy: float
    confidence: str  # HIGH, MEDIUM, LOW
    severity: str = "HIGH"
    description: str = ""
    category: str = ""  # cloud, vcs, messaging, database, crypto, generic

    def to_dict(self) -> dict[str, Any]:
        return {
            "secret_type": self.secret_type,
            "pattern_name": self.pattern_name,
            "value_masked": self.value_masked,
            "file": self.file,
            "line": self.line,
            "entropy": round(self.entropy, 2),
            "confidence": self.confidence,
            "severity": self.severity,
            "description": self.description,
            "category": self.category,
        }


@dataclass
class SecretsReport:
    """Aggregated secrets detection report."""

    secrets: list[DetectedSecret] = field(default_factory=list)
    files_scanned: int = 0
    patterns_used: int = 0
    entropy_threshold: float = 4.5
    allowlist_patterns: int = 0

    @property
    def exit_code(self) -> int:
        critical = sum(1 for s in self.secrets if s.severity == "CRITICAL")
        if critical > 0:
            return 2
        if self.secrets:
            return 1
        return 0

    def to_dict(self) -> dict[str, Any]:
        return {
            "total_secrets": len(self.secrets),
            "files_scanned": self.files_scanned,
            "patterns_used": self.patterns_used,
            "entropy_threshold": self.entropy_threshold,
            "allowlist_patterns": self.allowlist_patterns,
            "secrets": [s.to_dict() for s in self.secrets],
            "by_category": self._by_category(),
            "by_severity": self._by_severity(),
        }

    def _by_category(self) -> dict[str, int]:
        counts: dict[str, int] = {}
        for s in self.secrets:
            cat = s.category or "unknown"
            counts[cat] = counts.get(cat, 0) + 1
        return counts

    def _by_severity(self) -> dict[str, int]:
        counts: dict[str, int] = {}
        for s in self.secrets:
            counts[s.severity] = counts.get(s.severity, 0) + 1
        return counts


# ---------------------------------------------------------------------------
# Secret pattern definition
# ---------------------------------------------------------------------------

@dataclass
class SecretPatternDef:
    """Definition of a secret detection pattern."""

    name: str
    regex: re.Pattern[str]
    secret_type: str
    severity: str
    description: str
    category: str
    confidence: str = "HIGH"


# ---------------------------------------------------------------------------
# 30+ built-in patterns
# ---------------------------------------------------------------------------

_PATTERNS: list[SecretPatternDef] = [
    # --- Cloud: AWS ---
    SecretPatternDef(
        name="AWS Access Key ID",
        regex=re.compile(r"(AKIA[0-9A-Z]{16,})"),
        secret_type="aws_access_key",
        severity="CRITICAL",
        description="AWS Access Key ID detected",
        category="cloud",
    ),
    SecretPatternDef(
        name="AWS Secret Access Key",
        regex=re.compile(
            r"(?:aws_secret_access_key|secret_key|aws_secret)\s*[=:]\s*['\"]?([A-Za-z0-9/+=]{40})['\"]?"
        ),
        secret_type="aws_secret_key",
        severity="CRITICAL",
        description="AWS Secret Access Key detected",
        category="cloud",
    ),
    SecretPatternDef(
        name="AWS Session Token",
        regex=re.compile(
            r"(?:aws_session_token|session_token)\s*[=:]\s*['\"]?([A-Za-z0-9/+=]{100,})['\"]?"
        ),
        secret_type="aws_session_token",
        severity="CRITICAL",
        description="AWS Session Token detected",
        category="cloud",
    ),
    # --- Cloud: GCP ---
    SecretPatternDef(
        name="GCP Service Account Key",
        regex=re.compile(r'"type"\s*:\s*"service_account"'),
        secret_type="gcp_service_account",
        severity="CRITICAL",
        description="GCP Service Account JSON key file detected",
        category="cloud",
    ),
    SecretPatternDef(
        name="GCP API Key",
        regex=re.compile(r"(?:^|['\"\s=])(AIza[A-Za-z0-9_\-]{35})(?:['\"\s]|$)"),
        secret_type="gcp_api_key",
        severity="HIGH",
        description="Google/GCP API key detected",
        category="cloud",
    ),
    SecretPatternDef(
        name="GCP OAuth Client Secret",
        regex=re.compile(r"(?:client_secret)\s*[=:]\s*['\"]([A-Za-z0-9_\-]{24,})['\"]"),
        secret_type="gcp_oauth_secret",
        severity="HIGH",
        description="GCP OAuth client secret detected",
        category="cloud",
    ),
    # --- Cloud: Azure ---
    SecretPatternDef(
        name="Azure Storage Key",
        regex=re.compile(
            r"(?:DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=)([A-Za-z0-9+/=]{40,})"
        ),
        secret_type="azure_storage_key",
        severity="CRITICAL",
        description="Azure Storage account key detected",
        category="cloud",
    ),
    SecretPatternDef(
        name="Azure AD Client Secret",
        regex=re.compile(
            r"(?:AZURE_CLIENT_SECRET|azure_client_secret)\s*[=:]\s*['\"]?([A-Za-z0-9_\-.~]{30,})['\"]?"
        ),
        secret_type="azure_client_secret",
        severity="CRITICAL",
        description="Azure AD client secret detected",
        category="cloud",
    ),
    # --- VCS: GitHub ---
    SecretPatternDef(
        name="GitHub Personal Access Token",
        regex=re.compile(r"(?:^|['\"\s=])(ghp_[A-Za-z0-9]{36,})(?:['\"\s]|$)"),
        secret_type="github_pat",
        severity="CRITICAL",
        description="GitHub Personal Access Token detected",
        category="vcs",
    ),
    SecretPatternDef(
        name="GitHub OAuth Token",
        regex=re.compile(r"(?:^|['\"\s=])(gho_[A-Za-z0-9]{36,})(?:['\"\s]|$)"),
        secret_type="github_oauth",
        severity="CRITICAL",
        description="GitHub OAuth Access Token detected",
        category="vcs",
    ),
    SecretPatternDef(
        name="GitHub App Installation Token",
        regex=re.compile(r"(?:^|['\"\s=])(ghs_[A-Za-z0-9]{36,})(?:['\"\s]|$)"),
        secret_type="github_app_token",
        severity="CRITICAL",
        description="GitHub App Installation Token detected",
        category="vcs",
    ),
    SecretPatternDef(
        name="GitHub Fine-grained PAT",
        regex=re.compile(r"(?:^|['\"\s=])(github_pat_[A-Za-z0-9_]{30,})(?:['\"\s]|$)"),
        secret_type="github_fine_pat",
        severity="CRITICAL",
        description="GitHub Fine-grained Personal Access Token detected",
        category="vcs",
    ),
    SecretPatternDef(
        name="GitLab Personal Access Token",
        regex=re.compile(r"(?:^|['\"\s=])(glpat-[A-Za-z0-9_\-]{20,})(?:['\"\s]|$)"),
        secret_type="gitlab_pat",
        severity="CRITICAL",
        description="GitLab Personal Access Token detected",
        category="vcs",
    ),
    # --- Messaging: Slack ---
    SecretPatternDef(
        name="Slack Bot Token",
        regex=re.compile(r"(?:^|['\"\s=])(xoxb-[0-9]{10,}-[A-Za-z0-9\-]+)(?:['\"\s]|$)"),
        secret_type="slack_bot_token",
        severity="HIGH",
        description="Slack Bot token detected",
        category="messaging",
    ),
    SecretPatternDef(
        name="Slack User Token",
        regex=re.compile(r"(?:^|['\"\s=])(xoxp-[0-9]{10,}-[A-Za-z0-9\-]+)(?:['\"\s]|$)"),
        secret_type="slack_user_token",
        severity="HIGH",
        description="Slack User token detected",
        category="messaging",
    ),
    SecretPatternDef(
        name="Slack Webhook URL",
        regex=re.compile(
            r"https://hooks\.slack\.com/services/T[A-Za-z0-9]+/B[A-Za-z0-9]+/[A-Za-z0-9]+"
        ),
        secret_type="slack_webhook",
        severity="HIGH",
        description="Slack Webhook URL detected",
        category="messaging",
    ),
    SecretPatternDef(
        name="Discord Webhook URL",
        regex=re.compile(
            r"https://discord(?:app)?\.com/api/webhooks/\d+/[A-Za-z0-9_\-]+"
        ),
        secret_type="discord_webhook",
        severity="HIGH",
        description="Discord Webhook URL detected",
        category="messaging",
    ),
    SecretPatternDef(
        name="Telegram Bot Token",
        regex=re.compile(r"(?:^|['\"\s=])(\d{8,10}:[A-Za-z0-9_\-]{35})(?:['\"\s]|$)"),
        secret_type="telegram_bot_token",
        severity="HIGH",
        description="Telegram Bot token detected",
        category="messaging",
    ),
    # --- Auth: JWT ---
    SecretPatternDef(
        name="JWT Token",
        regex=re.compile(r"(?:^|['\"\s=])(eyJ[A-Za-z0-9_\-]+\.eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)(?:['\"\s]|$)"),
        secret_type="jwt_token",
        severity="HIGH",
        description="JWT token detected",
        category="auth",
    ),
    SecretPatternDef(
        name="JWT Secret",
        regex=re.compile(
            r"(?:jwt[_-]?secret|JWT_SECRET)\s*[=:]\s*['\"]([^'\"]{8,})['\"]",
            re.IGNORECASE,
        ),
        secret_type="jwt_secret",
        severity="CRITICAL",
        description="JWT Secret key detected",
        category="auth",
    ),
    # --- Crypto: Private Keys ---
    SecretPatternDef(
        name="RSA Private Key",
        regex=re.compile(r"-----BEGIN RSA PRIVATE KEY-----"),
        secret_type="rsa_private_key",
        severity="CRITICAL",
        description="RSA private key detected",
        category="crypto",
    ),
    SecretPatternDef(
        name="EC Private Key",
        regex=re.compile(r"-----BEGIN EC PRIVATE KEY-----"),
        secret_type="ec_private_key",
        severity="CRITICAL",
        description="EC private key detected",
        category="crypto",
    ),
    SecretPatternDef(
        name="Generic Private Key",
        regex=re.compile(r"-----BEGIN PRIVATE KEY-----"),
        secret_type="private_key",
        severity="CRITICAL",
        description="Private key detected",
        category="crypto",
    ),
    SecretPatternDef(
        name="PGP Private Key",
        regex=re.compile(r"-----BEGIN PGP PRIVATE KEY BLOCK-----"),
        secret_type="pgp_private_key",
        severity="CRITICAL",
        description="PGP private key block detected",
        category="crypto",
    ),
    SecretPatternDef(
        name="SSH Private Key (OpenSSH)",
        regex=re.compile(r"-----BEGIN OPENSSH PRIVATE KEY-----"),
        secret_type="ssh_private_key",
        severity="CRITICAL",
        description="OpenSSH private key detected",
        category="crypto",
    ),
    # --- Database ---
    SecretPatternDef(
        name="Database Connection String",
        regex=re.compile(
            r"(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp)://[^:\s]+:[^@\s]+@[^\s]+"
        ),
        secret_type="database_url",
        severity="CRITICAL",
        description="Database connection string with embedded credentials",
        category="database",
    ),
    SecretPatternDef(
        name="SQLite Connection with Password",
        regex=re.compile(
            r"(?:sqlite|sqlcipher)://[^\s]*password=[^\s&]+"
        ),
        secret_type="sqlite_password",
        severity="HIGH",
        description="SQLite/SQLCipher connection with password",
        category="database",
    ),
    # --- Payment ---
    SecretPatternDef(
        name="Stripe Secret Key",
        regex=re.compile(r"(?:^|['\"\s=])(sk_live_[A-Za-z0-9]{20,})(?:['\"\s]|$)"),
        secret_type="stripe_secret_key",
        severity="CRITICAL",
        description="Stripe live secret key detected",
        category="payment",
    ),
    SecretPatternDef(
        name="Stripe Restricted Key",
        regex=re.compile(r"(?:^|['\"\s=])(rk_live_[A-Za-z0-9]{20,})(?:['\"\s]|$)"),
        secret_type="stripe_restricted_key",
        severity="HIGH",
        description="Stripe live restricted key detected",
        category="payment",
    ),
    # --- Email ---
    SecretPatternDef(
        name="SendGrid API Key",
        regex=re.compile(r"(?:^|['\"\s=])(SG\.[A-Za-z0-9_\-]{22,}\.[A-Za-z0-9_\-]{22,})(?:['\"\s]|$)"),
        secret_type="sendgrid_api_key",
        severity="HIGH",
        description="SendGrid API key detected",
        category="email",
    ),
    SecretPatternDef(
        name="Mailgun API Key",
        regex=re.compile(r"(?:^|['\"\s=])(key-[A-Za-z0-9]{32})(?:['\"\s]|$)"),
        secret_type="mailgun_api_key",
        severity="HIGH",
        description="Mailgun API key detected",
        category="email",
    ),
    # --- Generic ---
    SecretPatternDef(
        name="Generic API Key Assignment",
        regex=re.compile(
            r"(?:api[_-]?key|apikey|api_secret)\s*[=:]\s*['\"]([A-Za-z0-9_\-]{16,})['\"]",
            re.IGNORECASE,
        ),
        secret_type="generic_api_key",
        severity="HIGH",
        description="Generic API key assignment detected",
        category="generic",
    ),
    SecretPatternDef(
        name="Hardcoded Password",
        regex=re.compile(
            r"(?:password|passwd|pwd)\s*[=:]\s*['\"]([^'\"]{6,})['\"]",
            re.IGNORECASE,
        ),
        secret_type="hardcoded_password",
        severity="HIGH",
        description="Hardcoded password detected",
        category="generic",
    ),
    SecretPatternDef(
        name="OAuth Client Secret",
        regex=re.compile(
            r"(?:client[_-]?secret|oauth[_-]?secret)\s*[=:]\s*['\"]([A-Za-z0-9_\-]{16,})['\"]",
            re.IGNORECASE,
        ),
        secret_type="oauth_client_secret",
        severity="HIGH",
        description="OAuth client secret detected",
        category="auth",
    ),
    SecretPatternDef(
        name="Twilio API Key",
        regex=re.compile(r"(?:^|['\"\s=])(SK[0-9a-fA-F]{32})(?:['\"\s]|$)"),
        secret_type="twilio_api_key",
        severity="HIGH",
        description="Twilio API key detected",
        category="messaging",
    ),
    SecretPatternDef(
        name="Heroku API Key",
        regex=re.compile(
            r"(?:heroku[_-]?api[_-]?key|HEROKU_API_KEY)\s*[=:]\s*['\"]?([A-Fa-f0-9-]{36})['\"]?",
            re.IGNORECASE,
        ),
        secret_type="heroku_api_key",
        severity="HIGH",
        description="Heroku API key detected",
        category="cloud",
    ),
    SecretPatternDef(
        name="Environment Variable Secret",
        regex=re.compile(
            r"^(?:SECRET|TOKEN|API_KEY|AUTH_TOKEN|ACCESS_KEY|PRIVATE_KEY|ENCRYPTION_KEY)"
            r"\s*=\s*['\"]?([^\s'\"]{8,})['\"]?\s*$",
            re.IGNORECASE | re.MULTILINE,
        ),
        secret_type="env_secret",
        severity="HIGH",
        description="Secret-like environment variable detected",
        category="generic",
    ),
]


def get_all_patterns() -> list[SecretPatternDef]:
    """Return all built-in secret detection patterns."""
    return list(_PATTERNS)


# ---------------------------------------------------------------------------
# Entropy calculation
# ---------------------------------------------------------------------------

def calculate_entropy(value: str) -> float:
    """Calculate Shannon entropy of a string in bits per character.

    Args:
        value: The string to measure.

    Returns:
        Shannon entropy (0.0 for empty/single-char).
    """
    if len(value) <= 1:
        return 0.0

    freq: dict[str, int] = {}
    for ch in value:
        freq[ch] = freq.get(ch, 0) + 1

    length = len(value)
    entropy = 0.0
    for count in freq.values():
        if count > 0:
            p = count / length
            entropy -= p * math.log2(p)

    return entropy


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _mask_value(value: str) -> str:
    """Mask a secret value, showing first 4 chars."""
    if len(value) <= 4:
        return value[0] + "***" if value else "***"
    return value[:4] + "***"


_PLACEHOLDER_VALUES = {
    "your_api_key", "your_secret", "changeme", "placeholder",
    "example", "test_key", "dummy", "xxxxxxxx", "your-api-key",
    "replace_me", "insert_here", "todo", "fixme", "your_token",
    "password123", "password", "secret", "none", "null", "undefined",
    "your_password", "your-secret", "your-token", "test123",
    "change_me", "sample", "demo",
}


def _is_false_positive(value: str) -> bool:
    """Check if a matched value is a likely false positive."""
    if len(value) < 6:
        return True
    lower = value.lower()
    for ph in _PLACEHOLDER_VALUES:
        if lower == ph or lower.startswith(ph):
            return True
    if len(set(value)) == 1:
        return True
    # All digits less than 8 chars
    if value.isdigit() and len(value) < 8:
        return True
    return False


def load_allowlist(path: str) -> list[re.Pattern[str]]:
    """Load allowlist patterns from a file.

    Each line is treated as a regex pattern. Empty lines and lines
    starting with # are ignored.

    Args:
        path: Path to allowlist file.

    Returns:
        List of compiled regex patterns.
    """
    patterns: list[re.Pattern[str]] = []
    filepath = Path(path)
    if not filepath.exists():
        return patterns
    for line in filepath.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        try:
            patterns.append(re.compile(line))
        except re.error:
            continue
    return patterns


def _matches_allowlist(value: str, allowlist: list[re.Pattern[str]]) -> bool:
    """Check if a value matches any allowlist pattern."""
    for pattern in allowlist:
        if pattern.search(value):
            return True
    return False


# ---------------------------------------------------------------------------
# Main detection API
# ---------------------------------------------------------------------------

def detect_secrets(
    source: str,
    filename: str = "<string>",
    entropy_threshold: float = 4.5,
    allowlist: list[re.Pattern[str]] | None = None,
) -> list[DetectedSecret]:
    """Scan source code for secrets using pattern matching and entropy analysis.

    Args:
        source: Source code or configuration content.
        filename: Name of the file being scanned.
        entropy_threshold: Minimum entropy for high-entropy detection.
        allowlist: Optional list of compiled regex patterns to ignore.

    Returns:
        List of DetectedSecret instances.
    """
    if allowlist is None:
        allowlist = []

    findings: list[DetectedSecret] = []
    lines = source.splitlines()
    seen: set[tuple[int, str]] = set()

    for pat in _PATTERNS:
        for i, line_text in enumerate(lines, start=1):
            for match in pat.regex.finditer(line_text):
                key = (i, pat.name)
                if key in seen:
                    continue
                seen.add(key)

                raw = match.group(1) if match.lastindex and match.lastindex >= 1 else match.group(0)
                raw = raw.strip().strip("'\"")

                if _is_false_positive(raw):
                    continue

                if _matches_allowlist(raw, allowlist):
                    continue

                entropy = calculate_entropy(raw)

                findings.append(DetectedSecret(
                    secret_type=pat.secret_type,
                    pattern_name=pat.name,
                    value_masked=_mask_value(raw),
                    file=filename,
                    line=i,
                    entropy=entropy,
                    confidence=pat.confidence,
                    severity=pat.severity,
                    description=pat.description,
                    category=pat.category,
                ))

    # High-entropy string detection
    _token_re = re.compile(r"['\"]([A-Za-z0-9+/=_\-]{20,})['\"]")
    for i, line_text in enumerate(lines, start=1):
        for match in _token_re.finditer(line_text):
            token = match.group(1)
            entropy = calculate_entropy(token)
            if entropy > entropy_threshold:
                key = (i, "high_entropy")
                if key in seen:
                    continue
                if any(f.line == i for f in findings):
                    continue
                seen.add(key)
                if _is_false_positive(token):
                    continue
                if _matches_allowlist(token, allowlist):
                    continue
                findings.append(DetectedSecret(
                    secret_type="high_entropy",
                    pattern_name="High Entropy String",
                    value_masked=_mask_value(token),
                    file=filename,
                    line=i,
                    entropy=entropy,
                    confidence="MEDIUM",
                    severity="MEDIUM",
                    description=f"High-entropy string ({entropy:.1f} bits/char)",
                    category="generic",
                ))

    return findings


def detect_secrets_file(
    path: str,
    entropy_threshold: float = 4.5,
    allowlist: list[re.Pattern[str]] | None = None,
) -> list[DetectedSecret]:
    """Scan a single file for secrets.

    Args:
        path: Path to the file.
        entropy_threshold: Minimum entropy for high-entropy detection.
        allowlist: Optional allowlist patterns.

    Returns:
        List of DetectedSecret instances.
    """
    filepath = Path(path)
    if not filepath.exists():
        return []
    try:
        source = filepath.read_text(encoding="utf-8", errors="replace")
    except (OSError, UnicodeDecodeError):
        return []
    return detect_secrets(source, filename=str(filepath),
                          entropy_threshold=entropy_threshold,
                          allowlist=allowlist)


def detect_secrets_directory(
    dirpath: str,
    entropy_threshold: float = 4.5,
    allowlist: list[re.Pattern[str]] | None = None,
) -> SecretsReport:
    """Scan all files in a directory recursively for secrets.

    Args:
        dirpath: Path to directory.
        entropy_threshold: Minimum entropy threshold.
        allowlist: Optional allowlist patterns.

    Returns:
        SecretsReport with all findings.
    """
    target = Path(dirpath)
    scan_extensions = {
        ".py", ".ts", ".js", ".go", ".env", ".yaml", ".yml",
        ".json", ".toml", ".cfg", ".ini", ".conf", ".properties",
        ".sh", ".bash", ".zsh", ".rb", ".java", ".kt",
    }

    all_secrets: list[DetectedSecret] = []
    files_count = 0

    if target.is_file():
        findings = detect_secrets_file(str(target), entropy_threshold, allowlist)
        return SecretsReport(
            secrets=findings,
            files_scanned=1,
            patterns_used=len(_PATTERNS),
            entropy_threshold=entropy_threshold,
            allowlist_patterns=len(allowlist) if allowlist else 0,
        )

    for filepath in sorted(target.rglob("*")):
        if filepath.is_file():
            if filepath.suffix in scan_extensions or filepath.name.startswith(".env"):
                findings = detect_secrets_file(
                    str(filepath), entropy_threshold, allowlist
                )
                all_secrets.extend(findings)
                files_count += 1

    return SecretsReport(
        secrets=all_secrets,
        files_scanned=files_count,
        patterns_used=len(_PATTERNS),
        entropy_threshold=entropy_threshold,
        allowlist_patterns=len(allowlist) if allowlist else 0,
    )


# ---------------------------------------------------------------------------
# Output formatters
# ---------------------------------------------------------------------------

def format_secrets_text(report: SecretsReport) -> str:
    """Format secrets report as human-readable text.

    Args:
        report: SecretsReport to format.

    Returns:
        Formatted text output.
    """
    lines: list[str] = []
    lines.append("")
    lines.append("=" * 60)
    lines.append("  MCP Secrets Detector — Results")
    lines.append("=" * 60)
    lines.append(f"  Files scanned: {report.files_scanned}")
    lines.append(f"  Patterns used: {report.patterns_used}")
    lines.append(f"  Entropy threshold: {report.entropy_threshold}")
    lines.append(f"  Secrets found: {len(report.secrets)}")
    if report.allowlist_patterns:
        lines.append(f"  Allowlist patterns: {report.allowlist_patterns}")
    lines.append("=" * 60)
    lines.append("")

    if not report.secrets:
        lines.append("  No secrets detected.")
        lines.append("")
        return "\n".join(lines)

    severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
    sorted_secrets = sorted(
        report.secrets,
        key=lambda s: severity_order.get(s.severity, 99),
    )

    for s in sorted_secrets:
        icon = {"CRITICAL": "[!!]", "HIGH": "[!]", "MEDIUM": "[-]"}.get(
            s.severity, "[.]"
        )
        lines.append(f"  {icon} [{s.severity}] {s.pattern_name}")
        lines.append(f"     File: {s.file}:{s.line}")
        lines.append(f"     Type: {s.secret_type} | Category: {s.category}")
        lines.append(f"     Masked: {s.value_masked}")
        lines.append(f"     Entropy: {s.entropy:.2f} bits/char | Confidence: {s.confidence}")
        if s.description:
            lines.append(f"     {s.description}")
        lines.append("")

    lines.append("=" * 60)
    return "\n".join(lines)


def format_secrets_json(report: SecretsReport) -> str:
    """Format secrets report as JSON.

    Args:
        report: SecretsReport to format.

    Returns:
        JSON string.
    """
    import json
    return json.dumps({"secrets_detector": report.to_dict()}, indent=2, ensure_ascii=False)
