"""
Paramiko Crypto Scanner - Regex-based detection for paramiko SSH library.

Catches quantum-vulnerable SSH key types, weak transport configurations,
and disabled host key verification in Python paramiko code.
"""

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

logger = logging.getLogger(__name__)


@dataclass
class ParamikoFinding:
    """Represents a crypto vulnerability found in Paramiko code."""
    file_path: str
    line_number: int
    column: int
    end_column: int
    rule_id: str
    severity: str
    message: str
    quantum_risk_score: int
    code_snippet: str


class ParamikoCryptoScanner:
    """Scanner for detecting weak cryptography in Python files using paramiko."""

    # ── Quantum Risk Score Framework (0-100) ───────────────────────────────
    # 95  Classically AND quantum broken
    # 85  Quantum-broken (Shor's), classically sound — RSA, ECDSA, DSA
    # 75  Quantum-weakened (Grover's)
    # 70  Security hygiene — disabled checks, unsafe configs
    # ──────────────────────────────────────────────────────────────────────

    PATTERNS = {
        # ── RSA key types ────────────────────────────────────────────────────
        'rsa_key_generate': {
            'pattern': r'paramiko\.RSAKey\.generate\s*\(',
            'message': 'paramiko.RSAKey.generate: RSA is broken by Shor\'s algorithm. Use Ed25519 now; plan post-quantum SSH migration.',
            'severity': 'ERROR',
            'risk_score': 85,
        },
        'rsa_key_from_private_key': {
            'pattern': r'paramiko\.RSAKey\.from_private_key(?:_file)?\s*\(',
            'message': 'paramiko.RSAKey loaded: RSA SSH keys are quantum-vulnerable. Migrate to Ed25519 keys.',
            'severity': 'ERROR',
            'risk_score': 85,
        },

        # ── DSA key types ────────────────────────────────────────────────────
        'dsa_key_generate': {
            'pattern': r'paramiko\.DSSKey\.generate\s*\(',
            'message': 'paramiko.DSSKey.generate: DSA is broken by Shor\'s algorithm and deprecated in OpenSSH 7.0+. Use Ed25519.',
            'severity': 'ERROR',
            'risk_score': 90,
        },
        'dsa_key_load': {
            'pattern': r'paramiko\.DSSKey\.from_private_key(?:_file)?\s*\(',
            'message': 'paramiko.DSSKey loaded: DSA SSH keys are quantum-vulnerable and classically deprecated.',
            'severity': 'ERROR',
            'risk_score': 90,
        },

        # ── ECDSA key types ──────────────────────────────────────────────────
        'ecdsa_key_generate': {
            'pattern': r'paramiko\.ECDSAKey\.generate\s*\(',
            'message': 'paramiko.ECDSAKey.generate: ECDSA is broken by Shor\'s algorithm. Use Ed25519 now; plan post-quantum SSH migration.',
            'severity': 'ERROR',
            'risk_score': 85,
        },
        'ecdsa_key_load': {
            'pattern': r'paramiko\.ECDSAKey\.from_private_key(?:_file)?\s*\(',
            'message': 'paramiko.ECDSAKey loaded: ECDSA SSH keys are quantum-vulnerable (Shor\'s algorithm).',
            'severity': 'ERROR',
            'risk_score': 85,
        },

        # ── Weak RSA key sizes ───────────────────────────────────────────────
        'rsa_weak_1024': {
            'pattern': r'paramiko\.RSAKey\.generate\s*\(\s*(?:bits\s*=\s*)?1024\b',
            'message': 'paramiko RSA 1024-bit key: classically weak and trivially quantum-broken. Minimum 2048 bits; prefer Ed25519.',
            'severity': 'ERROR',
            'risk_score': 95,
        },
        'rsa_weak_512': {
            'pattern': r'paramiko\.RSAKey\.generate\s*\(\s*(?:bits\s*=\s*)?512\b',
            'message': 'paramiko RSA 512-bit key: trivially broken classically and quantum-broken.',
            'severity': 'ERROR',
            'risk_score': 95,
        },
        'rsa_weak_2048': {
            'pattern': r'paramiko\.RSAKey\.generate\s*\(\s*(?:bits\s*=\s*)?2048\b',
            'message': 'paramiko RSA 2048-bit key: below NIST recommended 3072 bits and quantum-vulnerable at any size. Plan migration to Ed25519/post-quantum.',
            'severity': 'WARNING',
            'risk_score': 85,
        },

        # ── Host key verification bypasses ───────────────────────────────────
        'auto_add_policy': {
            'pattern': r'\.set_missing_host_key_policy\s*\(\s*paramiko\.AutoAddPolicy\s*\(',
            'message': 'paramiko AutoAddPolicy: host key checking disabled — trivial MITM. Use RejectPolicy with a maintained known_hosts file.',
            'severity': 'ERROR',
            'risk_score': 70,
        },
        'warning_policy': {
            'pattern': r'\.set_missing_host_key_policy\s*\(\s*paramiko\.WarningPolicy\s*\(',
            'message': 'paramiko WarningPolicy: host key accepted with only a warning — vulnerable to MITM. Use RejectPolicy.',
            'severity': 'WARNING',
            'risk_score': 70,
        },

        # ── Weak transport cipher/MAC configuration ──────────────────────────
        'disabled_algorithms': {
            'pattern': r'disabled_algorithms\s*=\s*\{',
            'message': 'paramiko disabled_algorithms config detected. Verify no secure algorithms are disabled without PQC replacements.',
            'severity': 'WARNING',
            'risk_score': 70,
        },
        'weak_cipher_3des': {
            'pattern': r'["\']3des-cbc["\']',
            'message': '3DES-CBC cipher in paramiko configuration: deprecated and quantum-vulnerable. Remove from preferred ciphers.',
            'severity': 'ERROR',
            'risk_score': 95,
        },
        'weak_mac_md5': {
            'pattern': r'["\']hmac-md5(?:-96)?["\']',
            'message': 'HMAC-MD5 MAC in paramiko configuration: MD5-based MACs are broken. Use hmac-sha2-256 or hmac-sha2-512.',
            'severity': 'ERROR',
            'risk_score': 95,
        },
        'weak_kex_diffie_hellman_group1': {
            'pattern': r'["\']diffie-hellman-group1-sha1["\']',
            'message': 'diffie-hellman-group1-sha1 KEX in paramiko: 1024-bit DH is classically weak and quantum-broken. Use curve25519-sha256.',
            'severity': 'ERROR',
            'risk_score': 95,
        },
        'weak_kex_diffie_hellman_group14': {
            'pattern': r'["\']diffie-hellman-group14-sha1["\']',
            'message': 'diffie-hellman-group14-sha1 KEX: SHA-1 is deprecated. Prefer curve25519-sha256 or diffie-hellman-group16-sha512.',
            'severity': 'WARNING',
            'risk_score': 85,
        },

        # ── Password authentication (not crypto per se, but security-relevant) ─
        'password_auth': {
            'pattern': r'\.connect\s*\([^)]*password\s*=',
            'message': 'paramiko password authentication: prefer public-key authentication. If password auth required, ensure strong passwords and MFA.',
            'severity': 'WARNING',
            'risk_score': 60,
        },
    }

    def scan_file(self, file_path: str) -> List[ParamikoFinding]:
        """Scan a Python file for Paramiko crypto vulnerabilities."""
        findings = []
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()

            # Only scan files that actually import paramiko
            if 'paramiko' not in content:
                return findings

            lines = content.splitlines()
            for line_num, line in enumerate(lines, start=1):
                stripped = line.strip()
                if stripped.startswith('#'):
                    continue
                for rule_id, pattern_info in self.PATTERNS.items():
                    for match in re.finditer(pattern_info['pattern'], line, re.IGNORECASE):
                        findings.append(ParamikoFinding(
                            file_path=file_path,
                            line_number=line_num,
                            column=match.start() + 1,
                            end_column=match.end() + 1,
                            rule_id=rule_id,
                            severity=pattern_info['severity'],
                            message=pattern_info['message'],
                            quantum_risk_score=pattern_info['risk_score'],
                            code_snippet=line.strip(),
                        ))
        except Exception as e:
            logger.exception(f"Error scanning {file_path}: {e}")
        return findings

    def format_findings(self, findings: List[ParamikoFinding]) -> Dict[str, Any]:
        """Format findings in scanner-compatible JSON format."""
        results = []
        for finding in findings:
            results.append({
                'check_id': f'paramiko-crypto-scanner.{finding.rule_id}',
                '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': ['python', 'paramiko', 'ssh'],
                        'quantum_vulnerable': True,
                        'quantum_risk_score': finding.quantum_risk_score,
                    },
                    'severity': finding.severity,
                    'lines': finding.code_snippet,
                },
            })
        return {'results': results, 'errors': [], 'version': 'paramiko-crypto-scanner-1.0'}
