"""Checker for hardcoded secrets and passwords.

Supports: Python, JavaScript/TypeScript, Go, Rust, Ruby, JSON, YAML
"""

import re
from pathlib import Path
from typing import List

from stablestack.checkers.base import BaseChecker
from stablestack.models import Finding, Severity, Tier


class HardcodedSecretsChecker(BaseChecker):
    """Detects hardcoded passwords, API keys, and secrets.

    Supports: Python, JavaScript/TypeScript, Go, Rust, Ruby, JSON, YAML
    """

    rule_id = "SEC001"
    rule_name = "hardcoded-secret"
    description = "Hardcoded secrets in code can be leaked and are hard to rotate"
    why = (
        "Hardcoding passwords or API keys in your code is dangerous. Anyone who sees "
        "your code (including in git history) can see your secrets. Use environment "
        "variables or a secrets manager instead."
    )
    severity = Severity.ERROR
    tier = Tier.CRITICAL  # Security issues are always critical
    file_extensions = {
        ".py", ".js", ".ts", ".jsx", ".tsx",
        ".go", ".rs", ".rb",
        ".env", ".json", ".yaml", ".yml"
    }

    # Patterns for secret assignment
    _SECRET_PATTERNS = [
        # password = "..."
        (re.compile(r'''(?i)(password|passwd|pwd|secret|api_key|apikey|api_secret|auth_token|access_token|private_key)\s*[=:]\s*['"`]([^'"`\n]{8,})['"`]'''), 'assignment'),
        # "password": "..."
        (re.compile(r'''(?i)['"]?(password|passwd|pwd|secret|api_key|apikey|api_secret|auth_token|access_token|private_key)['"]?\s*[=:]\s*['"`]([^'"`\n]{8,})['"`]'''), 'config'),
        # AWS keys
        (re.compile(r'''AKIA[0-9A-Z]{16}'''), 'aws_access_key'),
        # GitHub tokens
        (re.compile(r'''ghp_[a-zA-Z0-9]{36}'''), 'github_token'),
        (re.compile(r'''github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}'''), 'github_pat'),
        # Generic API keys (long hex or base64 strings)
        (re.compile(r'''(?i)(api[_-]?key|secret[_-]?key)\s*[=:]\s*['"`]([a-zA-Z0-9+/]{32,})['"`]'''), 'api_key'),
    ]

    # Values to ignore (placeholders, examples)
    _IGNORE_VALUES = {
        'your-api-key-here',
        'your_api_key_here',
        'xxx',
        'changeme',
        'placeholder',
        'example',
        'test',
        'dummy',
        'fake',
        'none',
        'null',
        'undefined',
        'todo',
        'replace_me',
        'insert_here',
    }

    def should_skip_file(self, path: Path) -> bool:
        """Skip test files and examples."""
        skip_patterns = ['test', 'spec', 'example', 'mock', 'fixture', 'sample']
        if any(p in path.stem.lower() for p in skip_patterns):
            return True
        return super().should_skip_file(path)

    def check_file(self, path: Path, content: str) -> List[Finding]:
        findings = []
        lines = content.splitlines()

        for pattern, pattern_type in self._SECRET_PATTERNS:
            for match in pattern.finditer(content):
                # Get the matched value
                groups = match.groups()
                if len(groups) >= 2:
                    secret_value = groups[1].lower()
                else:
                    secret_value = match.group(0).lower()

                # Skip placeholder values
                if any(ignore in secret_value for ignore in self._IGNORE_VALUES):
                    continue

                # Skip environment variable references
                if secret_value.startswith(('$', 'env.', 'process.env', 'os.environ')):
                    continue

                line_num = content[:match.start()].count('\n') + 1
                line_content = lines[line_num - 1] if line_num <= len(lines) else ""

                # Mask the secret in output
                masked = line_content[:20] + "..." if len(line_content) > 20 else line_content
                if pattern_type == 'aws_access_key':
                    msg = "AWS access key ID found in code"
                elif pattern_type == 'github_token':
                    msg = "GitHub token found in code"
                else:
                    msg = "Potential hardcoded secret found"

                findings.append(
                    self.create_finding(
                        path=path,
                        line=line_num,
                        column=match.start() - content.rfind('\n', 0, match.start()),
                        code=masked,
                        message=f"{msg}. Never commit secrets to version control!",
                        fix_hint=self._get_fix_hint(path),
                    )
                )

        return findings

    def _get_fix_hint(self, path: Path) -> str:
        suffix = path.suffix
        if suffix == '.go':
            return (
                "Use environment variables:\n"
                "apiKey := os.Getenv(\"API_KEY\")\n\n"
                "Or use a secrets manager like:\n"
                "- AWS Secrets Manager\n"
                "- HashiCorp Vault\n"
                "- godotenv for .env files"
            )
        elif suffix == '.rs':
            return (
                "Use environment variables:\n"
                "let api_key = std::env::var(\"API_KEY\").expect(\"API_KEY not set\");\n\n"
                "Or use a secrets manager like:\n"
                "- AWS Secrets Manager\n"
                "- HashiCorp Vault\n"
                "- dotenv crate for .env files"
            )
        elif suffix == '.rb':
            return (
                "Use environment variables:\n"
                "api_key = ENV['API_KEY']\n"
                "# or with Rails:\n"
                "Rails.application.credentials.api_key\n\n"
                "Or use a secrets manager like:\n"
                "- AWS Secrets Manager\n"
                "- HashiCorp Vault\n"
                "- dotenv gem for .env files"
            )
        else:
            return (
                "Use environment variables:\n"
                "Python: os.environ.get('API_KEY')\n"
                "JavaScript: process.env.API_KEY\n\n"
                "Or use a secrets manager like:\n"
                "- AWS Secrets Manager\n"
                "- HashiCorp Vault\n"
                "- dotenv files (not committed to git)"
            )
