"""
Apex Crypto Scanner - Regex-based detection for Salesforce Crypto API.

Since OpenGrep patterns don't match Apex Crypto class methods, this module
provides regex-based detection of weak cryptography in Apex code.
"""

import logging
import re
from dataclasses import dataclass
from pathlib import Path
from typing import List, Dict, Any

logger = logging.getLogger(__name__)


@dataclass
class ApexFinding:
    """Represents a crypto vulnerability found in Apex code."""
    file_path: str
    line_number: int
    column: int
    end_column: int
    function_name: str
    severity: str
    message: str
    quantum_risk_score: int
    code_snippet: str


class ApexCryptoScanner:
    """Scanner for detecting weak cryptography in Apex files."""

    # ── Quantum Risk Score Framework (0-100) ───────────────────────────────
    # 95  Classically AND quantum broken — MD5, SHA-1, DES, 3DES, RC4
    #     Broken even without quantum computers; Grover's/Shor's makes it worse
    # 90  Quantum-broken (Shor's) with additional classical weakness
    #     e.g. RSA-SHA1 (RSA broken by Shor's + SHA-1 is weak)
    # 85  Quantum-broken (Shor's), still classically sound
    #     e.g. ECC, RSA-SHA256, DH — safe today but broken by large quantum computer
    # 75  Quantum-weakened (Grover's halves effective key size)
    #     e.g. AES-128 → 64-bit effective; still needs quantum advantage to break
    # 70  Security hygiene — not an algorithm break, but unsafe practice
    #     e.g. Math.random() / non-crypto PRNG used for security-sensitive values
    # ──────────────────────────────────────────────────────────────────────

    # Regex patterns for Salesforce Crypto API
    PATTERNS = {
        'generateDigest_MD5': {
            'pattern': r"Crypto\.generateDigest\s*\(\s*['\"]MD5['\"]",
            'message': 'Crypto.generateDigest with MD5 detected - use SHA256 or SHA512',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'generateDigest_SHA1': {
            'pattern': r"Crypto\.generateDigest\s*\(\s*['\"]SHA1['\"]",
            'message': 'Crypto.generateDigest with SHA-1 detected - use SHA256 or SHA512',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'encrypt_DES': {
            'pattern': r"Crypto\.encrypt\s*\(\s*['\"]DES['\"]",
            'message': 'Crypto.encrypt with DES detected - use AES256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'encrypt_TripleDES': {
            'pattern': r"Crypto\.encrypt\s*\(\s*['\"]TripleDES['\"]",
            'message': 'Crypto.encrypt with 3DES detected - use AES256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'encryptWithManagedIV_AES128': {
            'pattern': r"Crypto\.encryptWithManagedIV\s*\(\s*['\"]AES128['\"]",
            'message': 'Crypto.encryptWithManagedIV with AES128 detected - use AES256',
            'severity': 'WARNING',
            'risk_score': 75
        },
        'decrypt_DES': {
            'pattern': r"Crypto\.decrypt\s*\(\s*['\"]DES['\"]",
            'message': 'Crypto.decrypt with DES detected - use AES256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'decrypt_TripleDES': {
            'pattern': r"Crypto\.decrypt\s*\(\s*['\"]TripleDES['\"]",
            'message': 'Crypto.decrypt with 3DES detected - use AES256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'decryptWithManagedIV_AES128': {
            'pattern': r"Crypto\.decryptWithManagedIV\s*\(\s*['\"]AES128['\"]",
            'message': 'Crypto.decryptWithManagedIV with AES128 detected - use AES256',
            'severity': 'WARNING',
            'risk_score': 75
        },
        'generateAesKey_128': {
            'pattern': r'Crypto\.generateAesKey\s*\(\s*128\s*\)',
            'message': 'Crypto.generateAesKey(128) detected - use 256-bit keys for quantum resistance',
            'severity': 'WARNING',
            'risk_score': 75
        },
        'sign_RSA_SHA1': {
            'pattern': r"Crypto\.sign\s*\(\s*['\"]RSA-SHA1['\"]",
            'message': 'Crypto.sign with RSA-SHA1 detected - use RSA-SHA256',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'sign_RSA_MD5': {
            'pattern': r"Crypto\.sign\s*\(\s*['\"]RSA-MD5['\"]",
            'message': 'Crypto.sign with RSA-MD5 detected - use RSA-SHA256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        # EncodingUtil (often used with weak crypto)
        'EncodingUtil_base64Encode_MD5': {
            'pattern': r'EncodingUtil\.base64Encode\s*\(\s*Crypto\.generateDigest\s*\(\s*[\'"]MD5[\'"]',
            'message': 'Base64 encoding of MD5 hash detected - use SHA256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        # RSA / ECC digital signatures (Shor-vulnerable)
        'sign_RSA_SHA256': {
            'pattern': r"Crypto\.sign\s*\(\s*['\"]RSA-SHA256['\"]",
            'message': 'Crypto.sign with RSA-SHA256 detected - RSA is quantum-vulnerable regardless of hash; migrate to post-quantum signatures',
            'severity': 'WARNING',
            'risk_score': 85
        },
        'generateDigitalSignature_RSA': {
            'pattern': r"Crypto\.generateDigitalSignature\s*\(\s*['\"]RSA['\"]",
            'message': 'Crypto.generateDigitalSignature with RSA detected - broken by Shor\'s algorithm; migrate to CRYSTALS-Dilithium',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'generateDigitalSignature_ECDSA': {
            'pattern': r"Crypto\.generateDigitalSignature\s*\(\s*['\"]ECDSA['\"]",
            'message': 'Crypto.generateDigitalSignature with ECDSA detected - ECC broken by Shor\'s algorithm; migrate to CRYSTALS-Dilithium or FALCON',
            'severity': 'WARNING',
            'risk_score': 85
        },
        # HMAC with broken hash functions
        'generateMac_HmacMD5': {
            'pattern': r"Crypto\.generateMac\s*\(\s*['\"]HmacMD5['\"]",
            'message': 'Crypto.generateMac with HmacMD5 detected - MD5 is cryptographically broken; use HmacSHA256 or HmacSHA512',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'generateMac_HmacSHA1': {
            'pattern': r"Crypto\.generateMac\s*\(\s*['\"]HmacSHA1['\"]",
            'message': 'Crypto.generateMac with HmacSHA1 detected - SHA-1 is weak; use HmacSHA256 or HmacSHA512',
            'severity': 'WARNING',
            'risk_score': 85
        },
        # Non-cryptographic PRNG
        'weak_random': {
            'pattern': r'Math\.random\s*\(\s*\)',
            'message': 'Math.random() is not cryptographically secure - use Crypto.getRandomInteger() or Crypto.generateAesKey()',
            'severity': 'WARNING',
            'risk_score': 70
        },
    }
    
    def scan_file(self, file_path: str) -> List[ApexFinding]:
        """Scan an Apex file for weak cryptography."""
        findings = []
        
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()
                
            for line_num, line in enumerate(lines, start=1):
                for func_name, pattern_info in self.PATTERNS.items():
                    pattern = pattern_info['pattern']
                    matches = re.finditer(pattern, line, re.IGNORECASE)
                    
                    for match in matches:
                        finding = ApexFinding(
                            file_path=file_path,
                            line_number=line_num,
                            column=match.start() + 1,
                            end_column=match.end() + 1,
                            function_name=func_name,
                            severity=pattern_info['severity'],
                            message=pattern_info['message'],
                            quantum_risk_score=pattern_info['risk_score'],
                            code_snippet=line.strip()
                        )
                        findings.append(finding)

        except Exception as e:
            logger.exception(f"Error scanning {file_path}: {e}")
            
        return findings
    
    def format_findings(self, findings: List[ApexFinding]) -> Dict[str, Any]:
        """Format findings in scanner-compatible JSON format."""
        results = []
        
        for finding in findings:
            result = {
                "check_id": f"apex-crypto-scanner.{finding.function_name.lower()}",
                "path": finding.file_path,
                "start": {"line": finding.line_number, "col": finding.column},
                "end": {"line": finding.line_number, "col": finding.end_column},
                "start_line": finding.line_number,
                "end_line": finding.line_number,
                "extra": {
                    "message": finding.message,
                    "metadata": {
                        "category": "security",
                        "technology": ["apex", "salesforce"],
                        "quantum_vulnerable": True,
                        "quantum_risk_score": finding.quantum_risk_score
                    },
                    "severity": finding.severity,
                    "lines": finding.code_snippet
                }
            }
            results.append(result)
            
        return {"results": results, "errors": [], "version": "apex-crypto-scanner-1.0"}
