"""Security checker for code review.

This module provides security-focused code analysis based on
OWASP Top 10 and common vulnerability patterns.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Optional

from ieum.review.analyzer import ReviewIssue, Severity, IssueCategory


class SecurityChecker:
    """Checks code for security vulnerabilities."""

    # Security patterns by language
    SECURITY_PATTERNS = {
        "python": {
            # Injection vulnerabilities
            "sql_injection_format": {
                "pattern": r"execute\s*\(\s*[\"'].*%[sd]",
                "severity": Severity.CRITICAL,
                "title": "Potential SQL Injection (format string)",
                "description": "String formatting in SQL query. Use parameterized queries.",
                "cwe": "CWE-89",
                "owasp": "A03:2021 - Injection",
            },
            "sql_injection_fstring": {
                "pattern": r"execute\s*\(\s*f[\"']",
                "severity": Severity.CRITICAL,
                "title": "Potential SQL Injection (f-string)",
                "description": "F-string in SQL query. Use parameterized queries.",
                "cwe": "CWE-89",
            },
            "sql_injection_concat": {
                "pattern": r"execute\s*\([^)]+\+",
                "severity": Severity.CRITICAL,
                "title": "Potential SQL Injection (concatenation)",
                "description": "String concatenation in SQL query. Use parameterized queries.",
                "cwe": "CWE-89",
            },
            "command_injection_os": {
                "pattern": r"os\.system\s*\(",
                "severity": Severity.HIGH,
                "title": "Potential Command Injection",
                "description": "os.system() is vulnerable to command injection. Use subprocess with shell=False.",
                "cwe": "CWE-78",
            },
            "command_injection_shell": {
                "pattern": r"subprocess\.\w+\s*\([^)]*shell\s*=\s*True",
                "severity": Severity.HIGH,
                "title": "Shell=True in subprocess",
                "description": "Using shell=True can lead to command injection. Use shell=False with argument list.",
                "cwe": "CWE-78",
            },
            "eval_usage": {
                "pattern": r"\beval\s*\(",
                "severity": Severity.HIGH,
                "title": "eval() usage",
                "description": "eval() can execute arbitrary code. Consider safer alternatives.",
                "cwe": "CWE-94",
            },
            "exec_usage": {
                "pattern": r"\bexec\s*\(",
                "severity": Severity.HIGH,
                "title": "exec() usage",
                "description": "exec() can execute arbitrary code. Consider safer alternatives.",
                "cwe": "CWE-94",
            },
            # Hardcoded secrets
            "hardcoded_password": {
                "pattern": r"(?:password|passwd|pwd)\s*=\s*[\"'][^\"']+[\"']",
                "severity": Severity.CRITICAL,
                "title": "Hardcoded password",
                "description": "Password appears to be hardcoded. Use environment variables or secret management.",
                "cwe": "CWE-798",
            },
            "hardcoded_api_key": {
                "pattern": r"(?:api_key|apikey|api_secret)\s*=\s*[\"'][^\"']+[\"']",
                "severity": Severity.CRITICAL,
                "title": "Hardcoded API key",
                "description": "API key appears to be hardcoded. Use environment variables.",
                "cwe": "CWE-798",
            },
            "hardcoded_token": {
                "pattern": r"(?:token|secret|auth)\s*=\s*[\"'][a-zA-Z0-9]{20,}[\"']",
                "severity": Severity.CRITICAL,
                "title": "Hardcoded token/secret",
                "description": "Token or secret appears to be hardcoded. Use secure secret management.",
                "cwe": "CWE-798",
            },
            # Insecure cryptography
            "weak_hash_md5": {
                "pattern": r"hashlib\.md5\s*\(",
                "severity": Severity.MEDIUM,
                "title": "Weak hash function (MD5)",
                "description": "MD5 is cryptographically broken. Use SHA-256 or better.",
                "cwe": "CWE-328",
            },
            "weak_hash_sha1": {
                "pattern": r"hashlib\.sha1\s*\(",
                "severity": Severity.MEDIUM,
                "title": "Weak hash function (SHA1)",
                "description": "SHA1 is deprecated for security use. Use SHA-256 or better.",
                "cwe": "CWE-328",
            },
            # Insecure deserialization
            "pickle_load": {
                "pattern": r"pickle\.loads?\s*\(",
                "severity": Severity.HIGH,
                "title": "Insecure deserialization (pickle)",
                "description": "Pickle can execute arbitrary code. Only unpickle trusted data.",
                "cwe": "CWE-502",
            },
            "yaml_load_unsafe": {
                "pattern": r"yaml\.load\s*\([^)]*(?:Loader\s*=\s*yaml\.(?:Unsafe)?Loader)?[^)]*\)",
                "severity": Severity.HIGH,
                "title": "Unsafe YAML loading",
                "description": "yaml.load() is unsafe. Use yaml.safe_load() instead.",
                "cwe": "CWE-502",
            },
            # Path traversal
            "path_traversal": {
                "pattern": r"open\s*\([^)]*\+[^)]*\)",
                "severity": Severity.MEDIUM,
                "title": "Potential path traversal",
                "description": "Dynamic file path construction. Validate and sanitize input paths.",
                "cwe": "CWE-22",
            },
            # Insecure SSL/TLS
            "ssl_verify_disabled": {
                "pattern": r"verify\s*=\s*False",
                "severity": Severity.HIGH,
                "title": "SSL verification disabled",
                "description": "Disabling SSL verification allows man-in-the-middle attacks.",
                "cwe": "CWE-295",
            },
        },
        "cpp": {
            # Buffer overflow
            "strcpy_usage": {
                "pattern": r"\bstrcpy\s*\(",
                "severity": Severity.CRITICAL,
                "title": "Unsafe strcpy usage",
                "description": "strcpy() can cause buffer overflow. Use strncpy() or std::string.",
                "cwe": "CWE-120",
            },
            "sprintf_usage": {
                "pattern": r"\bsprintf\s*\(",
                "severity": Severity.HIGH,
                "title": "Unsafe sprintf usage",
                "description": "sprintf() can cause buffer overflow. Use snprintf().",
                "cwe": "CWE-120",
            },
            "gets_usage": {
                "pattern": r"\bgets\s*\(",
                "severity": Severity.CRITICAL,
                "title": "gets() usage",
                "description": "gets() is inherently unsafe. Use fgets() instead.",
                "cwe": "CWE-120",
            },
            "scanf_usage": {
                "pattern": r"\bscanf\s*\(\s*[\"']%s",
                "severity": Severity.HIGH,
                "title": "Unsafe scanf with %s",
                "description": "%s in scanf can overflow. Specify maximum width.",
                "cwe": "CWE-120",
            },
            # Format string
            "printf_variable": {
                "pattern": r"printf\s*\(\s*[a-zA-Z_]\w*\s*\)",
                "severity": Severity.HIGH,
                "title": "Format string vulnerability",
                "description": "Variable as format string can lead to attacks. Use printf(\"%s\", var).",
                "cwe": "CWE-134",
            },
            # Integer overflow
            "unsafe_alloc": {
                "pattern": r"malloc\s*\([^)]*\*[^)]*\)",
                "severity": Severity.MEDIUM,
                "title": "Potential integer overflow in allocation",
                "description": "Multiplication in malloc size could overflow. Check for overflow first.",
                "cwe": "CWE-190",
            },
            # Memory issues
            "double_free_pattern": {
                "pattern": r"free\s*\([^)]+\).*free\s*\(\s*\1\s*\)",
                "severity": Severity.CRITICAL,
                "title": "Potential double free",
                "description": "Same pointer may be freed twice. Set pointer to NULL after free.",
                "cwe": "CWE-415",
            },
            # Hardcoded credentials
            "hardcoded_password": {
                "pattern": r"(?:password|passwd|pwd)\s*=\s*[\"'][^\"']+[\"']",
                "severity": Severity.CRITICAL,
                "title": "Hardcoded password",
                "description": "Password appears to be hardcoded. Use secure configuration.",
                "cwe": "CWE-798",
            },
        },
        "mfc": {
            # MFC-specific patterns inherit from cpp
            "strcpy_usage": {
                "pattern": r"\b(?:_tcscpy|lstrcpy|wcscpy)\s*\(",
                "severity": Severity.CRITICAL,
                "title": "Unsafe string copy",
                "description": "Use _tcscpy_s or StringCchCopy for safe string copying.",
                "cwe": "CWE-120",
            },
            "sprintf_usage": {
                "pattern": r"\b(?:_stprintf|wsprintf)\s*\(",
                "severity": Severity.HIGH,
                "title": "Unsafe string formatting",
                "description": "Use _stprintf_s or StringCchPrintf for safe formatting.",
                "cwe": "CWE-120",
            },
            "handle_leak": {
                "pattern": r"(?:CreateFile|CreateThread|CreateEvent|CreateMutex)\s*\([^;]+;(?![^}]*CloseHandle)",
                "severity": Severity.MEDIUM,
                "title": "Potential handle leak",
                "description": "Windows handle may not be closed. Ensure CloseHandle is called.",
                "cwe": "CWE-404",
            },
        },
    }

    def __init__(self):
        """Initialize the security checker."""
        pass

    def check_file(self, file_path: str | Path) -> list[ReviewIssue]:
        """Check a file for security issues.

        Args:
            file_path: Path to the file.

        Returns:
            List of security issues found.
        """
        file_path = Path(file_path)
        issues = []

        try:
            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read()
                lines = content.split("\n")
        except Exception:
            return issues

        language = self._detect_language(file_path)
        patterns = self.SECURITY_PATTERNS.get(language, {})

        # If MFC, also check cpp patterns
        if language == "mfc":
            patterns = {**self.SECURITY_PATTERNS.get("cpp", {}), **patterns}

        for rule_id, rule in patterns.items():
            pattern = re.compile(rule["pattern"], re.IGNORECASE | re.MULTILINE)

            for i, line in enumerate(lines, 1):
                if pattern.search(line):
                    issues.append(ReviewIssue(
                        severity=rule["severity"],
                        category=IssueCategory.SECURITY,
                        title=rule["title"],
                        description=rule["description"],
                        file_path=str(file_path),
                        line=i,
                        suggestion=rule.get("suggestion"),
                        code_snippet=line.strip()[:100],
                        rule_id=f"security/{rule_id}",
                    ))

        return issues

    def check_code(
        self,
        code: str,
        language: str,
        file_path: str = "<code>",
    ) -> list[ReviewIssue]:
        """Check code string for security issues.

        Args:
            code: Code to check.
            language: Programming language.
            file_path: Virtual file path.

        Returns:
            List of security issues.
        """
        issues = []
        lines = code.split("\n")
        patterns = self.SECURITY_PATTERNS.get(language, {})

        for rule_id, rule in patterns.items():
            pattern = re.compile(rule["pattern"], re.IGNORECASE | re.MULTILINE)

            for i, line in enumerate(lines, 1):
                if pattern.search(line):
                    issues.append(ReviewIssue(
                        severity=rule["severity"],
                        category=IssueCategory.SECURITY,
                        title=rule["title"],
                        description=rule["description"],
                        file_path=file_path,
                        line=i,
                        code_snippet=line.strip()[:100],
                        rule_id=f"security/{rule_id}",
                    ))

        return issues

    def check_for_secrets(self, content: str, file_path: str = "<code>") -> list[ReviewIssue]:
        """Check for hardcoded secrets.

        Args:
            content: Content to check.
            file_path: File path for reporting.

        Returns:
            List of secret-related issues.
        """
        issues = []
        lines = content.split("\n")

        # General patterns for secrets
        secret_patterns = [
            (r"(?:api[_-]?key|apikey)\s*[:=]\s*[\"'][a-zA-Z0-9]{16,}[\"']", "API key"),
            (r"(?:secret|private[_-]?key)\s*[:=]\s*[\"'][^\"']{20,}[\"']", "Secret key"),
            (r"(?:password|passwd|pwd)\s*[:=]\s*[\"'][^\"']+[\"']", "Password"),
            (r"(?:token|bearer)\s*[:=]\s*[\"'][a-zA-Z0-9._-]{20,}[\"']", "Token"),
            (r"(?:aws[_-]?access[_-]?key)\s*[:=]\s*[\"']AKIA[A-Z0-9]{16}[\"']", "AWS Access Key"),
            (r"(?:-----BEGIN (?:RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----)", "Private key"),
        ]

        for pattern, secret_type in secret_patterns:
            regex = re.compile(pattern, re.IGNORECASE)

            for i, line in enumerate(lines, 1):
                if regex.search(line):
                    issues.append(ReviewIssue(
                        severity=Severity.CRITICAL,
                        category=IssueCategory.SECURITY,
                        title=f"Potential hardcoded {secret_type}",
                        description=f"A {secret_type.lower()} may be hardcoded. Use environment variables or secret management.",
                        file_path=file_path,
                        line=i,
                        code_snippet=line[:50] + "..." if len(line) > 50 else line,
                        rule_id=f"security/hardcoded_{secret_type.lower().replace(' ', '_')}",
                    ))

        return issues

    def _detect_language(self, file_path: Path) -> str:
        """Detect programming language."""
        suffix = file_path.suffix.lower()

        if suffix in (".py", ".pyi"):
            return "python"
        elif suffix in (".cpp", ".cxx", ".cc", ".c", ".h", ".hpp"):
            # Check for MFC markers
            try:
                with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                    content = f.read(2000)
                    if any(marker in content for marker in ["<afxwin.h>", "CWinApp", "BEGIN_MESSAGE_MAP"]):
                        return "mfc"
            except Exception:
                pass
            return "cpp"
        elif suffix in (".js", ".jsx"):
            return "javascript"
        elif suffix in (".ts", ".tsx"):
            return "typescript"

        return "unknown"

    def get_owasp_category(self, issue: ReviewIssue) -> Optional[str]:
        """Get OWASP Top 10 category for an issue.

        Args:
            issue: Review issue.

        Returns:
            OWASP category or None.
        """
        # Map rule IDs to OWASP categories
        owasp_mapping = {
            "sql_injection": "A03:2021 - Injection",
            "command_injection": "A03:2021 - Injection",
            "eval_usage": "A03:2021 - Injection",
            "exec_usage": "A03:2021 - Injection",
            "hardcoded_password": "A07:2021 - Identification and Authentication Failures",
            "hardcoded_api_key": "A07:2021 - Identification and Authentication Failures",
            "hardcoded_token": "A07:2021 - Identification and Authentication Failures",
            "weak_hash": "A02:2021 - Cryptographic Failures",
            "ssl_verify_disabled": "A02:2021 - Cryptographic Failures",
            "pickle_load": "A08:2021 - Software and Data Integrity Failures",
            "yaml_load": "A08:2021 - Software and Data Integrity Failures",
            "path_traversal": "A01:2021 - Broken Access Control",
            "strcpy": "A03:2021 - Injection",
            "sprintf": "A03:2021 - Injection",
            "buffer": "A03:2021 - Injection",
        }

        if issue.rule_id:
            for pattern, category in owasp_mapping.items():
                if pattern in issue.rule_id.lower():
                    return category

        return None
