"""AP014: Hardcoded Secrets Detection."""

from collections.abc import Iterator
from pathlib import Path

from apiposture.core.models.endpoint import Endpoint
from apiposture.core.models.enums import Severity
from apiposture.core.models.finding import Finding

from apiposture_pro.rules.base import ProSecurityRule
from apiposture_pro.rules.secrets.patterns import SECRET_PATTERNS, is_placeholder


class AP014HardcodedSecrets(ProSecurityRule):
    """
    AP014: Hardcoded Secrets Detection.

    Scans source files for hardcoded secrets including:
    - AWS keys
    - API keys (GitHub, Stripe, SendGrid, etc.)
    - Passwords and tokens
    - Database credentials
    - Private keys
    """

    # Track files we've already scanned to avoid duplicates
    _scanned_files: set[Path] = set()

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

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

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

    @property
    def description(self) -> str:
        return (
            "Detects hardcoded secrets in source code including API keys, "
            "passwords, tokens, and credentials"
        )

    def evaluate(self, endpoint: Endpoint) -> Iterator[Finding]:
        """Scan endpoint's source file for hardcoded secrets."""
        # Only scan each file once
        if endpoint.file_path in self._scanned_files:
            return

        self._scanned_files.add(endpoint.file_path)

        # Read the source file
        try:
            content = endpoint.file_path.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            # File not readable, skip
            return

        # Split into lines for better reporting
        lines = content.split("\n")

        # Scan for each secret pattern
        for secret_type, (pattern, severity_str) in SECRET_PATTERNS.items():
            for line_num, line in enumerate(lines, start=1):
                # Skip comments
                stripped = line.strip()
                if stripped.startswith("#"):
                    continue

                # Search for pattern
                matches = pattern.finditer(line)
                for match in matches:
                    # Get the matched value
                    matched_value = match.group(1) if match.groups() else match.group(0)

                    # Skip known placeholders
                    if is_placeholder(matched_value):
                        continue

                    # Create finding
                    severity_map = {
                        "CRITICAL": Severity.CRITICAL,
                        "HIGH": Severity.HIGH,
                        "MEDIUM": Severity.MEDIUM,
                        "LOW": Severity.LOW,
                    }
                    finding_severity = severity_map.get(severity_str, Severity.HIGH)

                    # Mask the secret in the message
                    masked_value = matched_value[:4] + "*" * (len(matched_value) - 4)

                    yield self.create_finding(
                        endpoint,
                        message=(
                            f"Hardcoded {secret_type} found in {endpoint.file_path}:{line_num}. "
                            f"Value starts with: {masked_value}"
                        ),
                        recommendation=(
                            f"Remove hardcoded {secret_type} from source code. "
                            "Use environment variables, secret management services "
                            "(AWS Secrets Manager, HashiCorp Vault), or encrypted config files. "
                            "Never commit secrets to version control. "
                            "If already committed, rotate the credentials immediately."
                        ),
                        severity=finding_severity,
                    )
