"""
SQL Crypto Scanner - Regex-based quantum crypto vulnerability detection for SQL files.

Since OpenGrep doesn't support SQL as a language, this module provides pattern-based
detection of weak cryptographic functions in SQL 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 SQLFinding:
    """Represents a crypto vulnerability found in SQL 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 SQLCryptoScanner:
    """Scanner for detecting weak cryptography in SQL 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 weak crypto functions
    PATTERNS = {
        # MySQL
        'MD5': {
            'pattern': r'\bMD5\s*\(',
            'message': 'MD5 hash function detected - cryptographically broken and quantum vulnerable',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'SHA1': {
            'pattern': r'\bSHA1\s*\(',
            'message': 'SHA-1 hash function detected - collision attacks possible, quantum vulnerable',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'DES_ENCRYPT': {
            'pattern': r'\bDES_ENCRYPT\s*\(',
            'message': 'DES encryption detected - broken cipher, quantum vulnerable',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'DES_DECRYPT': {
            'pattern': r'\bDES_DECRYPT\s*\(',
            'message': 'DES decryption detected - broken cipher, quantum vulnerable',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'AES_ENCRYPT': {
            'pattern': r'\bAES_ENCRYPT\s*\(',
            'message': 'AES_ENCRYPT detected - verify key size is 256-bit for quantum resistance',
            'severity': 'WARNING',
            'risk_score': 75
        },
        
        # PostgreSQL
        'crypt_md5': {
            'pattern': r"\bcrypt\s*\([^,]+,\s*gen_salt\s*\(\s*['\"]md5['\"]",
            'message': 'crypt() with MD5 salt detected - use bcrypt or scrypt instead',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'digest_md5': {
            'pattern': r"\bdigest\s*\([^,]+,\s*['\"]md5['\"]",
            'message': 'digest() with MD5 detected - use SHA-256 or SHA-3',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'digest_sha1': {
            'pattern': r"\bdigest\s*\([^,]+,\s*['\"]sha1['\"]",
            'message': 'digest() with SHA-1 detected - use SHA-256 or SHA-3',
            'severity': 'ERROR',
            'risk_score': 90
        },
        
        # SQL Server
        'HASHBYTES_MD5': {
            'pattern': r"\bHASHBYTES\s*\(\s*['\"]MD5['\"]",
            'message': 'HASHBYTES with MD5 detected - use SHA2_256 or SHA2_512',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'HASHBYTES_SHA1': {
            'pattern': r"\bHASHBYTES\s*\(\s*['\"]SHA1['\"]",
            'message': 'HASHBYTES with SHA-1 detected - use SHA2_256 or SHA2_512',
            'severity': 'ERROR',
            'risk_score': 90
        },
        
        # Oracle
        'DBMS_CRYPTO_MD5': {
            'pattern': r'\bDBMS_CRYPTO\.HASH\s*\([^)]*HASH_MD5',
            'message': 'DBMS_CRYPTO.HASH with MD5 detected - use HASH_SHA256 or HASH_SHA512',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'DBMS_CRYPTO_SHA1': {
            'pattern': r'\bDBMS_CRYPTO\.HASH\s*\([^)]*HASH_SHA1',
            'message': 'DBMS_CRYPTO.HASH with SHA-1 detected - use HASH_SHA256 or HASH_SHA512',
            'severity': 'ERROR',
            'risk_score': 90
        },
        'DBMS_CRYPTO_DES': {
            'pattern': r'\bDBMS_CRYPTO\.ENCRYPT\s*\([^)]*ENCRYPT_DES',
            'message': 'DBMS_CRYPTO.ENCRYPT with DES detected - use ENCRYPT_AES256',
            'severity': 'ERROR',
            'risk_score': 95
        },
        'DBMS_CRYPTO_3DES': {
            'pattern': r'\bDBMS_CRYPTO\.ENCRYPT\s*\([^)]*ENCRYPT_3DES',
            'message': 'DBMS_CRYPTO.ENCRYPT with 3DES detected - use ENCRYPT_AES256',
            'severity': 'ERROR',
            'risk_score': 95
        },
    }
    
    def scan_file(self, file_path: str) -> List[SQLFinding]:
        """Scan a SQL 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 = SQLFinding(
                            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[SQLFinding]) -> Dict[str, Any]:
        """Format findings in scanner-compatible JSON format."""
        results = []
        
        for finding in findings:
            result = {
                "check_id": f"sql-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": ["sql"],
                        "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": "sql-crypto-scanner-1.0"}
