"""
Malware Sandbox - Safe malware analysis environment

Provides tools for defensive malware analysis:
- Static analysis (no execution)
- Indicator extraction (IOCs)
- YARA rule matching
- VirusTotal integration
- Behavior prediction

For security research and threat intelligence only.
NEVER execute malware outside isolated environments.
"""

import hashlib
import json
import os
import re
import shutil
import subprocess
import tempfile
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Optional

from groknroll.mode2600.core import get_mode2600


class MalwareCategory(Enum):
    """Malware categories."""

    RANSOMWARE = "ransomware"
    TROJAN = "trojan"
    RAT = "rat"
    BACKDOOR = "backdoor"
    WORM = "worm"
    VIRUS = "virus"
    ROOTKIT = "rootkit"
    SPYWARE = "spyware"
    ADWARE = "adware"
    CRYPTOMINER = "cryptominer"
    BOTNET = "botnet"
    DROPPER = "dropper"
    LOADER = "loader"
    INFOSTEALER = "infostealer"
    KEYLOGGER = "keylogger"
    UNKNOWN = "unknown"


class ThreatLevel(Enum):
    """Threat severity levels."""

    CRITICAL = "critical"
    HIGH = "high"
    MEDIUM = "medium"
    LOW = "low"
    INFO = "info"
    UNKNOWN = "unknown"


@dataclass
class ThreatIndicators:
    """Indicators of Compromise (IOCs) from malware analysis."""

    # File hashes
    md5: str = ""
    sha1: str = ""
    sha256: str = ""
    ssdeep: str = ""
    imphash: str = ""

    # Network indicators
    domains: list[str] = field(default_factory=list)
    ips: list[str] = field(default_factory=list)
    urls: list[str] = field(default_factory=list)

    # File indicators
    mutexes: list[str] = field(default_factory=list)
    registry_keys: list[str] = field(default_factory=list)
    file_paths: list[str] = field(default_factory=list)

    # Crypto
    bitcoin_addresses: list[str] = field(default_factory=list)
    ethereum_addresses: list[str] = field(default_factory=list)
    monero_addresses: list[str] = field(default_factory=list)

    # Other
    email_addresses: list[str] = field(default_factory=list)
    pdb_paths: list[str] = field(default_factory=list)

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "hashes": {
                "md5": self.md5,
                "sha1": self.sha1,
                "sha256": self.sha256,
                "ssdeep": self.ssdeep,
                "imphash": self.imphash,
            },
            "network": {
                "domains": self.domains,
                "ips": self.ips,
                "urls": self.urls,
            },
            "file": {
                "mutexes": self.mutexes,
                "registry_keys": self.registry_keys,
                "file_paths": self.file_paths,
            },
            "crypto": {
                "bitcoin": self.bitcoin_addresses,
                "ethereum": self.ethereum_addresses,
                "monero": self.monero_addresses,
            },
            "other": {
                "emails": self.email_addresses,
                "pdb_paths": self.pdb_paths,
            },
        }


@dataclass
class YaraMatch:
    """YARA rule match result."""

    rule_name: str
    tags: list[str] = field(default_factory=list)
    meta: dict[str, Any] = field(default_factory=dict)
    strings: list[dict] = field(default_factory=list)


@dataclass
class SandboxResult:
    """Complete malware analysis result."""

    # Basic info
    filename: str
    filesize: int
    filetype: str

    # Classification
    category: MalwareCategory = MalwareCategory.UNKNOWN
    threat_level: ThreatLevel = ThreatLevel.UNKNOWN
    family: Optional[str] = None

    # Indicators
    indicators: ThreatIndicators = field(default_factory=ThreatIndicators)

    # YARA matches
    yara_matches: list[YaraMatch] = field(default_factory=list)

    # Analysis details
    suspicious_strings: list[str] = field(default_factory=list)
    suspicious_imports: list[str] = field(default_factory=list)
    packed: bool = False
    packer: Optional[str] = None

    # Scores
    static_score: float = 0.0
    virustotal_score: Optional[str] = None

    # Timestamps
    analyzed_at: datetime = field(default_factory=datetime.utcnow)

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "basic": {
                "filename": self.filename,
                "filesize": self.filesize,
                "filetype": self.filetype,
            },
            "classification": {
                "category": self.category.value,
                "threat_level": self.threat_level.value,
                "family": self.family,
            },
            "indicators": self.indicators.to_dict(),
            "yara_matches": [
                {"rule": m.rule_name, "tags": m.tags}
                for m in self.yara_matches
            ],
            "analysis": {
                "suspicious_strings": self.suspicious_strings[:20],
                "suspicious_imports": self.suspicious_imports,
                "packed": self.packed,
                "packer": self.packer,
            },
            "scores": {
                "static": self.static_score,
                "virustotal": self.virustotal_score,
            },
            "analyzed_at": self.analyzed_at.isoformat(),
        }


class MalwareSandbox:
    """
    Safe malware analysis sandbox.

    Performs STATIC ANALYSIS ONLY - no code execution.

    Features:
    - Hash calculation (MD5, SHA1, SHA256, ssdeep)
    - IOC extraction (IPs, domains, URLs, crypto addresses)
    - YARA rule matching
    - Packer detection
    - Import analysis
    - String extraction and categorization

    For dynamic analysis, use isolated VMs/containers.
    """

    # Suspicious Windows API imports
    SUSPICIOUS_IMPORTS = {
        # Process manipulation
        "CreateRemoteThread", "WriteProcessMemory", "VirtualAllocEx",
        "OpenProcess", "NtCreateThreadEx", "RtlCreateUserThread",
        # Code injection
        "SetWindowsHookEx", "CreateToolhelp32Snapshot", "Module32First",
        "Process32First", "Thread32First",
        # Persistence
        "RegSetValueEx", "RegCreateKeyEx", "CreateService",
        # Anti-debugging
        "IsDebuggerPresent", "CheckRemoteDebuggerPresent",
        "NtQueryInformationProcess", "OutputDebugString",
        # Crypto
        "CryptEncrypt", "CryptDecrypt", "CryptGenKey",
        # Network
        "InternetOpen", "URLDownloadToFile", "WinHttpOpen",
        "HttpSendRequest", "socket", "connect", "send", "recv",
        # Keylogging
        "GetAsyncKeyState", "SetWindowsHookExA", "GetKeyState",
        # Screen capture
        "BitBlt", "GetDC", "CreateCompatibleDC",
        # File operations
        "DeleteFile", "MoveFile", "CopyFile",
        # Privilege escalation
        "AdjustTokenPrivileges", "OpenProcessToken",
    }

    # Suspicious strings patterns
    SUSPICIOUS_PATTERNS = [
        # Commands
        (r"cmd\.exe", "command_execution"),
        (r"powershell", "powershell"),
        (r"/bin/sh|/bin/bash", "shell"),
        # Persistence
        (r"HKEY_LOCAL_MACHINE|HKLM", "registry"),
        (r"\\CurrentVersion\\Run", "autorun"),
        (r"schtasks|at\.exe", "scheduled_task"),
        # Anti-analysis
        (r"vmware|virtualbox|vbox|qemu|sandbox", "anti_vm"),
        (r"wireshark|fiddler|procmon|ollydbg|x64dbg", "anti_analysis"),
        # Crypto
        (r"bitcoin|wallet|ransom|decrypt|encrypt", "ransomware"),
        (r"-----BEGIN.*KEY-----", "crypto_key"),
        # Network
        (r"http://|https://|ftp://", "url"),
        (r"\.onion", "tor"),
        # Data exfil
        (r"password|passwd|credential|login", "credential_theft"),
        (r"credit.?card|cvv|expir", "financial"),
    ]

    def __init__(self, yara_rules_path: Optional[Path] = None):
        """
        Initialize malware sandbox.

        Args:
            yara_rules_path: Path to YARA rules directory
        """
        self.mode2600 = get_mode2600()
        self.yara_rules_path = yara_rules_path

    def analyze(
        self,
        sample_path: str | Path,
        extract_iocs: bool = True,
        run_yara: bool = True,
    ) -> SandboxResult:
        """
        Analyze a malware sample (static analysis only).

        Args:
            sample_path: Path to sample file
            extract_iocs: Extract indicators of compromise
            run_yara: Run YARA rules

        Returns:
            SandboxResult with analysis details
        """
        context = self.mode2600.require_context()

        path = Path(sample_path)
        if not path.exists():
            raise FileNotFoundError(f"Sample not found: {path}")

        # Log operation
        self.mode2600.log_operation(
            "analyze_sample",
            "malware_sandbox",
            {"path": str(path)},
            f"Analyzing sample: {path.name} (static only)",
            True,
        )

        # Initialize result
        result = SandboxResult(
            filename=path.name,
            filesize=path.stat().st_size,
            filetype=self._identify_type(path),
        )

        # Calculate hashes
        result.indicators = self._calculate_hashes(path)

        # Read file content for analysis
        with open(path, "rb") as f:
            content = f.read()

        # Extract strings and IOCs
        if extract_iocs:
            self._extract_iocs(content, result)

        # Analyze imports (for PE files)
        if result.filetype.startswith("PE"):
            self._analyze_imports(path, result)

        # Check for packing
        self._detect_packer(path, content, result)

        # Run YARA rules
        if run_yara:
            self._run_yara(path, result)

        # Calculate threat score
        self._calculate_score(result)

        # Classify malware
        self._classify(result)

        return result

    def _identify_type(self, path: Path) -> str:
        """Identify file type."""
        if shutil.which("file"):
            result = subprocess.run(
                ["file", "-b", str(path)],
                capture_output=True,
                text=True,
            )
            return result.stdout.strip()[:100]
        return "unknown"

    def _calculate_hashes(self, path: Path) -> ThreatIndicators:
        """Calculate file hashes."""
        indicators = ThreatIndicators()

        with open(path, "rb") as f:
            content = f.read()

        indicators.md5 = hashlib.md5(content).hexdigest()
        indicators.sha1 = hashlib.sha1(content).hexdigest()
        indicators.sha256 = hashlib.sha256(content).hexdigest()

        # ssdeep if available
        if shutil.which("ssdeep"):
            result = subprocess.run(
                ["ssdeep", "-b", str(path)],
                capture_output=True,
                text=True,
            )
            if result.stdout:
                indicators.ssdeep = result.stdout.strip().split(",")[-1]

        return indicators

    def _extract_iocs(self, content: bytes, result: SandboxResult) -> None:
        """Extract indicators of compromise from content."""
        # Convert to string for regex
        try:
            text = content.decode("utf-8", errors="ignore")
        except:
            text = content.decode("latin-1", errors="ignore")

        # IP addresses
        ip_pattern = r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
        result.indicators.ips = list(set(re.findall(ip_pattern, text)))

        # Filter out common false positives
        result.indicators.ips = [
            ip for ip in result.indicators.ips
            if not ip.startswith("0.") and not ip.startswith("127.")
        ]

        # Domains
        domain_pattern = r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}"
        domains = set(re.findall(domain_pattern, text))
        # Filter common false positives
        result.indicators.domains = [
            d for d in domains
            if len(d) > 5 and not d.endswith(".dll") and not d.endswith(".exe")
        ][:50]  # Limit

        # URLs
        url_pattern = r"https?://[^\s<>\"']+|ftp://[^\s<>\"']+"
        result.indicators.urls = list(set(re.findall(url_pattern, text)))[:50]

        # Bitcoin addresses
        btc_pattern = r"\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b|bc1[a-zA-HJ-NP-Z0-9]{39,59}\b"
        result.indicators.bitcoin_addresses = list(set(re.findall(btc_pattern, text)))

        # Ethereum addresses
        eth_pattern = r"\b0x[a-fA-F0-9]{40}\b"
        result.indicators.ethereum_addresses = list(set(re.findall(eth_pattern, text)))

        # Monero addresses
        xmr_pattern = r"\b4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}\b"
        result.indicators.monero_addresses = list(set(re.findall(xmr_pattern, text)))

        # Email addresses
        email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
        result.indicators.email_addresses = list(set(re.findall(email_pattern, text)))[:20]

        # Registry keys (Windows)
        reg_pattern = r"HKEY_[A-Z_]+\\[^\s\"']+"
        result.indicators.registry_keys = list(set(re.findall(reg_pattern, text)))[:20]

        # PDB paths
        pdb_pattern = r"[A-Za-z]:\\[^\x00]+\.pdb"
        result.indicators.pdb_paths = list(set(re.findall(pdb_pattern, text)))

        # Check suspicious patterns
        for pattern, category in self.SUSPICIOUS_PATTERNS:
            matches = re.findall(pattern, text, re.IGNORECASE)
            if matches:
                for match in matches[:5]:
                    result.suspicious_strings.append(f"[{category}] {match}")

    def _analyze_imports(self, path: Path, result: SandboxResult) -> None:
        """Analyze PE imports for suspicious APIs."""
        # Try to use objdump or pefile
        if shutil.which("objdump"):
            try:
                output = subprocess.run(
                    ["objdump", "-p", str(path)],
                    capture_output=True,
                    text=True,
                    timeout=30,
                )

                for line in output.stdout.split("\n"):
                    for suspicious in self.SUSPICIOUS_IMPORTS:
                        if suspicious.lower() in line.lower():
                            result.suspicious_imports.append(suspicious)

                result.suspicious_imports = list(set(result.suspicious_imports))

            except Exception:
                pass

    def _detect_packer(self, path: Path, content: bytes, result: SandboxResult) -> None:
        """Detect if sample is packed."""
        # Check entropy
        entropy = self._calculate_entropy(content)
        if entropy > 7.5:
            result.packed = True

        # Check for known packer signatures
        packer_signatures = {
            b"UPX!": "UPX",
            b"PEC2": "PECompact",
            b"MPRESS": "MPRESS",
            b"ASPack": "ASPack",
            b"Themida": "Themida",
            b"VMProtect": "VMProtect",
        }

        for sig, name in packer_signatures.items():
            if sig in content:
                result.packed = True
                result.packer = name
                break

        # Try DIE (Detect It Easy) if available
        if shutil.which("diec"):
            try:
                output = subprocess.run(
                    ["diec", str(path)],
                    capture_output=True,
                    text=True,
                    timeout=30,
                )

                if "packer" in output.stdout.lower():
                    result.packed = True
                    # Extract packer name
                    for line in output.stdout.split("\n"):
                        if "packer" in line.lower():
                            result.packer = line.strip()[:50]
                            break

            except Exception:
                pass

    def _calculate_entropy(self, data: bytes) -> float:
        """Calculate Shannon entropy of data."""
        import math
        from collections import Counter

        if not data:
            return 0.0

        counts = Counter(data)
        length = len(data)

        entropy = 0.0
        for count in counts.values():
            p = count / length
            entropy -= p * math.log2(p)

        return entropy

    def _run_yara(self, path: Path, result: SandboxResult) -> None:
        """Run YARA rules against sample."""
        if not shutil.which("yara"):
            return

        rules_paths = []

        # Check for rules
        if self.yara_rules_path and self.yara_rules_path.exists():
            rules_paths.append(self.yara_rules_path)

        # Check common locations
        common_paths = [
            Path("/usr/share/yara-rules"),
            Path.home() / "yara-rules",
            Path.home() / ".yara",
        ]

        for p in common_paths:
            if p.exists():
                rules_paths.append(p)

        for rules_path in rules_paths:
            try:
                # Find all .yar/.yara files
                for rule_file in rules_path.glob("**/*.yar*"):
                    try:
                        output = subprocess.run(
                            ["yara", str(rule_file), str(path)],
                            capture_output=True,
                            text=True,
                            timeout=30,
                        )

                        for line in output.stdout.strip().split("\n"):
                            if line:
                                parts = line.split()
                                if parts:
                                    result.yara_matches.append(
                                        YaraMatch(rule_name=parts[0])
                                    )

                    except Exception:
                        continue

            except Exception:
                continue

    def _calculate_score(self, result: SandboxResult) -> None:
        """Calculate threat score based on findings."""
        score = 0.0

        # Suspicious imports (0-30 points)
        score += min(len(result.suspicious_imports) * 3, 30)

        # Suspicious strings (0-20 points)
        score += min(len(result.suspicious_strings) * 2, 20)

        # Packing (0-15 points)
        if result.packed:
            score += 15

        # YARA matches (0-25 points)
        score += min(len(result.yara_matches) * 5, 25)

        # Network indicators (0-10 points)
        if result.indicators.urls or result.indicators.domains:
            score += 10

        # Crypto addresses (strong ransomware indicator)
        if result.indicators.bitcoin_addresses or result.indicators.monero_addresses:
            score += 20

        result.static_score = min(score, 100)

    def _classify(self, result: SandboxResult) -> None:
        """Classify malware based on indicators."""
        # Set threat level based on score
        if result.static_score >= 80:
            result.threat_level = ThreatLevel.CRITICAL
        elif result.static_score >= 60:
            result.threat_level = ThreatLevel.HIGH
        elif result.static_score >= 40:
            result.threat_level = ThreatLevel.MEDIUM
        elif result.static_score >= 20:
            result.threat_level = ThreatLevel.LOW
        else:
            result.threat_level = ThreatLevel.INFO

        # Try to classify category
        strings_lower = " ".join(result.suspicious_strings).lower()

        if result.indicators.bitcoin_addresses or "ransom" in strings_lower:
            result.category = MalwareCategory.RANSOMWARE
        elif "keylog" in strings_lower or "GetAsyncKeyState" in result.suspicious_imports:
            result.category = MalwareCategory.KEYLOGGER
        elif "stealer" in strings_lower or "credential" in strings_lower:
            result.category = MalwareCategory.INFOSTEALER
        elif "miner" in strings_lower or "stratum" in strings_lower:
            result.category = MalwareCategory.CRYPTOMINER
        elif "backdoor" in strings_lower:
            result.category = MalwareCategory.BACKDOOR
        elif "CreateRemoteThread" in result.suspicious_imports:
            result.category = MalwareCategory.TROJAN

    def check_virustotal(
        self,
        sample_hash: str,
        api_key: Optional[str] = None,
    ) -> Optional[dict]:
        """
        Check hash against VirusTotal.

        Args:
            sample_hash: MD5, SHA1, or SHA256 hash
            api_key: VirusTotal API key (from env if not provided)

        Returns:
            VT report or None
        """
        api_key = api_key or os.environ.get("VIRUSTOTAL_API_KEY")
        if not api_key:
            return None

        try:
            import requests

            headers = {"x-apikey": api_key}
            url = f"https://www.virustotal.com/api/v3/files/{sample_hash}"

            response = requests.get(url, headers=headers, timeout=30)

            if response.status_code == 200:
                data = response.json()
                stats = data.get("data", {}).get("attributes", {}).get("last_analysis_stats", {})
                return {
                    "malicious": stats.get("malicious", 0),
                    "suspicious": stats.get("suspicious", 0),
                    "undetected": stats.get("undetected", 0),
                    "harmless": stats.get("harmless", 0),
                }

        except Exception:
            pass

        return None


# Convenience function


def analyze_sample(
    sample_path: str | Path,
    extract_iocs: bool = True,
    run_yara: bool = True,
) -> SandboxResult:
    """
    Analyze a malware sample (static analysis only).

    Args:
        sample_path: Path to sample
        extract_iocs: Extract indicators
        run_yara: Run YARA rules

    Returns:
        SandboxResult
    """
    return MalwareSandbox().analyze(sample_path, extract_iocs, run_yara)
