"""Spyware detector for AYN Antivirus.

Scans files and system state for indicators of spyware: keyloggers, screen
capture utilities, data exfiltration patterns, reverse shells, unauthorized
SSH keys, and suspicious shell-profile modifications.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import List, Optional

from ayn_antivirus.constants import SUSPICIOUS_CRON_PATTERNS
from ayn_antivirus.detectors.base import BaseDetector, DetectionResult

# ---------------------------------------------------------------------------
# File-content patterns
# ---------------------------------------------------------------------------

# Keylogger indicators.
_RE_KEYLOGGER = re.compile(
    rb"(?:"
    rb"/dev/input/event\d+"
    rb"|xinput\s+(?:test|list)"
    rb"|xdotool\b"
    rb"|showkey\b"
    rb"|logkeys\b"
    rb"|pynput\.keyboard"
    rb"|keyboard\.on_press"
    rb"|evdev\.InputDevice"
    rb"|GetAsyncKeyState"
    rb"|SetWindowsHookEx"
    rb")",
    re.IGNORECASE,
)

# Screen / audio capture.
_RE_SCREEN_CAPTURE = re.compile(
    rb"(?:"
    rb"scrot\b"
    rb"|import\s+-window\s+root"
    rb"|xwd\b"
    rb"|ffmpeg\s+.*-f\s+x11grab"
    rb"|xdpyinfo"
    rb"|ImageGrab\.grab"
    rb"|screenshot"
    rb"|pyautogui\.screenshot"
    rb"|screencapture\b"
    rb")",
    re.IGNORECASE,
)

_RE_AUDIO_CAPTURE = re.compile(
    rb"(?:"
    rb"arecord\b"
    rb"|parecord\b"
    rb"|ffmpeg\s+.*-f\s+(?:alsa|pulse|avfoundation)"
    rb"|pyaudio"
    rb"|sounddevice"
    rb")",
    re.IGNORECASE,
)

# Data exfiltration.
_RE_EXFIL = re.compile(
    rb"(?:"
    rb"curl\s+.*-[FdT]\s"
    rb"|curl\s+.*--upload-file"
    rb"|wget\s+.*--post-file"
    rb"|scp\s+.*@"
    rb"|rsync\s+.*@"
    rb"|nc\s+-[^\s]*\s+\d+\s*<"
    rb"|python[23]?\s+-m\s+http\.server"
    rb")",
    re.IGNORECASE,
)

# Reverse shell.
_RE_REVERSE_SHELL = re.compile(
    rb"(?:"
    rb"bash\s+-i\s+>&\s*/dev/tcp/"
    rb"|nc\s+-e\s+/bin/"
    rb"|ncat\s+.*-e\s+/bin/"
    rb"|socat\s+exec:"
    rb"|python[23]?\s+-c\s+['\"]import\s+socket"
    rb"|perl\s+-e\s+['\"]use\s+Socket"
    rb"|ruby\s+-rsocket\s+-e"
    rb"|php\s+-r\s+['\"].*fsockopen"
    rb"|mkfifo\s+/tmp/.*;\s*nc"
    rb"|/dev/tcp/\d+\.\d+\.\d+\.\d+"
    rb")",
    re.IGNORECASE,
)

# Suspicious cron patterns (compiled from constants).
_RE_CRON_PATTERNS = [
    re.compile(pat.encode(), re.IGNORECASE) for pat in SUSPICIOUS_CRON_PATTERNS
]


class SpywareDetector(BaseDetector):
    """Detect spyware indicators in files and on the host."""

    # ------------------------------------------------------------------
    # BaseDetector interface
    # ------------------------------------------------------------------

    @property
    def name(self) -> str:
        return "spyware_detector"

    @property
    def description(self) -> str:
        return "Detects keyloggers, screen capture, data exfiltration, and reverse shells"

    def detect(
        self,
        file_path: str | Path,
        file_content: Optional[bytes] = None,
        file_hash: Optional[str] = None,
    ) -> List[DetectionResult]:
        file_path = Path(file_path)
        results: List[DetectionResult] = []

        try:
            content = self._read_content(file_path, file_content)
        except OSError as exc:
            self._warn("Cannot read %s: %s", file_path, exc)
            return results

        # --- File-content checks ---
        results.extend(self._check_keylogger(file_path, content))
        results.extend(self._check_screen_capture(file_path, content))
        results.extend(self._check_audio_capture(file_path, content))
        results.extend(self._check_exfiltration(file_path, content))
        results.extend(self._check_reverse_shell(file_path, content))
        results.extend(self._check_hidden_cron(file_path, content))

        # --- Host-state checks (only for relevant paths) ---
        results.extend(self._check_authorized_keys(file_path, content))
        results.extend(self._check_shell_profile(file_path, content))

        return results

    # ------------------------------------------------------------------
    # Keylogger patterns
    # ------------------------------------------------------------------

    def _check_keylogger(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        matches = _RE_KEYLOGGER.findall(content)
        if matches:
            samples = sorted(set(m.decode(errors="replace") for m in matches[:5]))
            results.append(DetectionResult(
                threat_name="Spyware.Keylogger",
                threat_type="SPYWARE",
                severity="CRITICAL",
                confidence=80,
                details=f"Keylogger indicators: {', '.join(samples)}",
                detector_name=self.name,
            ))
        return results

    # ------------------------------------------------------------------
    # Screen capture
    # ------------------------------------------------------------------

    def _check_screen_capture(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        if _RE_SCREEN_CAPTURE.search(content):
            results.append(DetectionResult(
                threat_name="Spyware.ScreenCapture",
                threat_type="SPYWARE",
                severity="HIGH",
                confidence=70,
                details="Screen-capture tools or API calls detected",
                detector_name=self.name,
            ))
        return results

    # ------------------------------------------------------------------
    # Audio capture
    # ------------------------------------------------------------------

    def _check_audio_capture(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        if _RE_AUDIO_CAPTURE.search(content):
            results.append(DetectionResult(
                threat_name="Spyware.AudioCapture",
                threat_type="SPYWARE",
                severity="HIGH",
                confidence=65,
                details="Audio recording tools or API calls detected",
                detector_name=self.name,
            ))
        return results

    # ------------------------------------------------------------------
    # Data exfiltration
    # ------------------------------------------------------------------

    def _check_exfiltration(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        matches = _RE_EXFIL.findall(content)
        if matches:
            samples = [m.decode(errors="replace")[:80] for m in matches[:3]]
            results.append(DetectionResult(
                threat_name="Spyware.DataExfiltration",
                threat_type="SPYWARE",
                severity="HIGH",
                confidence=70,
                details=f"Data exfiltration pattern(s): {'; '.join(samples)}",
                detector_name=self.name,
            ))
        return results

    # ------------------------------------------------------------------
    # Reverse shell
    # ------------------------------------------------------------------

    def _check_reverse_shell(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        match = _RE_REVERSE_SHELL.search(content)
        if match:
            results.append(DetectionResult(
                threat_name="Spyware.ReverseShell",
                threat_type="SPYWARE",
                severity="CRITICAL",
                confidence=90,
                details=f"Reverse shell pattern: {match.group()[:100]!r}",
                detector_name=self.name,
            ))
        return results

    # ------------------------------------------------------------------
    # Hidden cron jobs
    # ------------------------------------------------------------------

    def _check_hidden_cron(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []

        # Only check cron-related files.
        path_str = str(file_path)
        is_cron = any(tok in path_str for tok in ("cron", "crontab", "/var/spool/"))
        if not is_cron:
            return results

        for pat in _RE_CRON_PATTERNS:
            match = pat.search(content)
            if match:
                results.append(DetectionResult(
                    threat_name="Spyware.Cron.SuspiciousEntry",
                    threat_type="SPYWARE",
                    severity="HIGH",
                    confidence=80,
                    details=f"Suspicious cron pattern in {file_path}: {match.group()[:80]!r}",
                    detector_name=self.name,
                ))

        return results

    # ------------------------------------------------------------------
    # Unauthorized SSH keys
    # ------------------------------------------------------------------

    def _check_authorized_keys(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        if file_path.name != "authorized_keys":
            return results

        # Flag if the file exists in an unexpected location.
        path_str = str(file_path)
        if not path_str.startswith("/root/") and "/.ssh/" not in path_str:
            results.append(DetectionResult(
                threat_name="Spyware.SSH.UnauthorizedKeysFile",
                threat_type="SPYWARE",
                severity="HIGH",
                confidence=75,
                details=f"authorized_keys found in unexpected location: {file_path}",
                detector_name=self.name,
            ))

        # Check for suspiciously many keys.
        key_count = content.count(b"ssh-rsa") + content.count(b"ssh-ed25519") + content.count(b"ecdsa-sha2")
        if key_count > 10:
            results.append(DetectionResult(
                threat_name="Spyware.SSH.ExcessiveKeys",
                threat_type="SPYWARE",
                severity="MEDIUM",
                confidence=55,
                details=f"{key_count} SSH keys in {file_path} — possible unauthorized access",
                detector_name=self.name,
            ))

        # command= prefix can force a shell command on login — often abused.
        if b'command="' in content or b"command='" in content:
            results.append(DetectionResult(
                threat_name="Spyware.SSH.ForcedCommand",
                threat_type="SPYWARE",
                severity="MEDIUM",
                confidence=60,
                details=f"Forced command found in authorized_keys: {file_path}",
                detector_name=self.name,
            ))

        return results

    # ------------------------------------------------------------------
    # Shell profile modifications
    # ------------------------------------------------------------------

    _PROFILE_FILES = {
        ".bashrc", ".bash_profile", ".profile", ".zshrc",
        ".bash_login", ".bash_logout",
    }

    _RE_PROFILE_SUSPICIOUS = re.compile(
        rb"(?:"
        rb"curl\s+[^\n]*\|\s*(?:sh|bash)"
        rb"|wget\s+[^\n]*\|\s*(?:sh|bash)"
        rb"|/dev/tcp/"
        rb"|base64\s+--decode"
        rb"|nohup\s+.*&"
        rb"|eval\s+\$\("
        rb"|python[23]?\s+-c\s+['\"]import\s+(?:socket|os|pty)"
        rb")",
        re.IGNORECASE,
    )

    def _check_shell_profile(
        self, file_path: Path, content: bytes
    ) -> List[DetectionResult]:
        results: List[DetectionResult] = []
        if file_path.name not in self._PROFILE_FILES:
            return results

        match = self._RE_PROFILE_SUSPICIOUS.search(content)
        if match:
            results.append(DetectionResult(
                threat_name="Spyware.ShellProfile.SuspiciousEntry",
                threat_type="SPYWARE",
                severity="CRITICAL",
                confidence=85,
                details=(
                    f"Suspicious command in shell profile {file_path}: "
                    f"{match.group()[:100]!r}"
                ),
                detector_name=self.name,
            ))

        return results
