"""CRYPTO-001: Weak Cryptographic Hash detection rule."""

from __future__ import annotations

import ast
import re

from . import Rule
from ..models import Finding, Severity


# Detection patterns for weak hash algorithms in scanned target code
WEAK_HASH_PATTERNS: list[tuple[str, str]] = [
    (r'\bhashlib\.md5\b', "MD5"),
    (r'\bhashlib\.sha1\b', "SHA1"),
    (r'\bhmac\.new\b.*\bmd5\b', "HMAC-MD5"),
    (r'\bhmac\.new\b.*\bsha1\b', "HMAC-SHA1"),
]


class WeakCryptoHashRule(Rule):
    """Detects use of weak cryptographic hash algorithms (MD5, SHA1)."""

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

    @property
    def name(self) -> str:
        return "Weak Cryptographic Hash"

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

    @property
    def cwe(self) -> str:
        return "CWE-328"

    @property
    def description(self) -> str:
        return "Weak hash algorithm (MD5/SHA1) used for security-sensitive operations."

    @property
    def detailed_description(self) -> str:
        return (
            "Weak hash algorithm (MD5/SHA1) used for security-sensitive operations.\n\n"
            "MD5 and SHA1 are cryptographically broken and should not be used for\n"
            "password hashing, digital signatures, or integrity verification.\n"
            "Collisions can be generated in seconds for MD5 and hours for SHA1.\n\n"
            "Examples of vulnerable code:\n"
            "  hashlib.md5(password.encode())\n"
            "  hashlib.sha1(data)\n\n"
            "How to fix:\n"
            "  - Use hashlib.sha256() or hashlib.sha3_256() for integrity\n"
            "  - Use bcrypt, scrypt, or argon2 for password hashing\n"
            "  - Exception: MD5 is acceptable for non-security checksums"
        )

    def suggest_fix(self, source: str, finding: Finding) -> str | None:
        """Replace MD5/SHA1 with SHA256."""
        lines = source.splitlines()
        idx = finding.line - 1
        if idx < 0 or idx >= len(lines):
            return None
        line = lines[idx]
        fixed = line
        if re.search(r'\bhashlib\.md5\b', fixed):
            fixed = re.sub(r'\bhashlib\.md5\b', 'hashlib.sha256', fixed)
        if re.search(r'\bhashlib\.sha1\b', fixed):
            fixed = re.sub(r'\bhashlib\.sha1\b', 'hashlib.sha256', fixed)
        if fixed == line:
            indent = line[: len(line) - len(line.lstrip())]
            return f"{indent}# TODO: Replace weak hash with hashlib.sha256()"
        return fixed

    def check(self, tree: ast.Module, source: str) -> list[Finding]:
        """Check for weak cryptographic hash usage."""
        findings: list[Finding] = []
        lines = self._get_lines(source)

        for i, line in enumerate(lines, 1):
            if line.strip().startswith("#"):
                continue
            for pattern, algo_name in WEAK_HASH_PATTERNS:
                if re.search(pattern, line, re.IGNORECASE):
                    findings.append(Finding(
                        rule_id=self.rule_id,
                        severity=self.severity,
                        title=f"Weak hash algorithm: {algo_name}",
                        description=f"{algo_name} is cryptographically weak.",
                        file="",
                        line=i,
                        code_snippet=line.strip()[:80],
                        recommendation="Use hashlib.sha256() or stronger.",
                        cwe="CWE-328",
                        confidence="MEDIUM",
                    ))
                    break  # One finding per line
        return findings
