"""Security scanner for imported content."""

from __future__ import annotations

import re
from typing import Any

from gaol.sharing.models import (
    SecurityFinding,
    SecurityScanResult,
    Severity,
)


class SecurityScanner:
    """Scan profiles and specs for security issues.

    Checks for:
    - Command injection patterns (piped curl/wget to shell)
    - Dangerous commands (rm -rf /, chmod 777)
    - Credential exposure (hardcoded passwords, API keys)
    - Privilege escalation (privileged mode, dangerous capabilities)
    - Sensitive mount paths (docker.sock, /etc/shadow, ~/.ssh)
    """

    # Suspicious command patterns
    SUSPICIOUS_COMMANDS: list[tuple[str, str, Severity]] = [
        # Pattern, Description, Severity
        (r"curl\s+[^|]*\|\s*(ba)?sh", "Piped curl to shell", Severity.CRITICAL),
        (r"wget\s+[^|]*\|\s*(ba)?sh", "Piped wget to shell", Severity.CRITICAL),
        (r"curl\s+[^|]*\|\s*sudo", "Piped curl to sudo", Severity.CRITICAL),
        (r"rm\s+-rf\s+/\s*$", "Dangerous rm -rf /", Severity.CRITICAL),
        (r"rm\s+-rf\s+/\*", "Dangerous rm -rf /*", Severity.CRITICAL),
        (r"mkfs\.", "Filesystem formatting command", Severity.CRITICAL),
        (r"dd\s+.*of=/dev/", "Direct disk write with dd", Severity.CRITICAL),
        (r"chmod\s+777\s+", "World-writable permissions", Severity.WARNING),
        (r"chmod\s+-R\s+777", "Recursive world-writable", Severity.WARNING),
        (r"\beval\s+", "Use of eval", Severity.WARNING),
        (r">\s*/dev/sd[a-z]", "Direct write to disk device", Severity.CRITICAL),
    ]

    # Sensitive mount paths
    SENSITIVE_PATHS: list[tuple[str, str, Severity]] = [
        # Path pattern, Description, Severity
        (r"/var/run/docker\.sock", "Docker socket mount", Severity.CRITICAL),
        (r"/run/docker\.sock", "Docker socket mount", Severity.CRITICAL),
        (r"/etc/shadow", "Shadow password file", Severity.WARNING),
        (r"/etc/passwd", "Password file", Severity.INFO),
        (r"/etc/sudoers", "Sudoers file", Severity.WARNING),
        (r"~?/\.ssh", "SSH directory", Severity.WARNING),
        (r"~?/\.gnupg", "GPG directory", Severity.WARNING),
        (r"~?/\.aws", "AWS credentials", Severity.WARNING),
        (r"~?/\.kube", "Kubernetes config", Severity.WARNING),
        (r"/root", "Root home directory", Severity.WARNING),
    ]

    # Credential patterns in environment variables
    CREDENTIAL_PATTERNS: list[tuple[str, str]] = [
        # Pattern, Description
        (r"^password$", "Password variable"),
        (r"^passwd$", "Password variable"),
        (r"^secret$", "Secret variable"),
        (r"^api[_-]?key$", "API key variable"),
        (r"^auth[_-]?token$", "Auth token variable"),
        (r"^access[_-]?token$", "Access token variable"),
        (r"^private[_-]?key$", "Private key variable"),
    ]

    # Credential value patterns (detect hardcoded credentials)
    CREDENTIAL_VALUE_PATTERNS: list[tuple[str, str]] = [
        (r"^[a-zA-Z0-9+/]{40,}={0,2}$", "Possible base64 encoded secret"),
        (r"^ghp_[a-zA-Z0-9]{36}$", "GitHub personal access token"),
        (r"^gho_[a-zA-Z0-9]{36}$", "GitHub OAuth token"),
        (r"^sk-[a-zA-Z0-9]{48}$", "OpenAI API key"),
        (r"^AKIA[A-Z0-9]{16}$", "AWS access key ID"),
    ]

    # Dangerous capabilities
    DANGEROUS_CAPABILITIES: list[tuple[str, str, Severity]] = [
        ("ALL", "All capabilities granted", Severity.CRITICAL),
        ("SYS_ADMIN", "SYS_ADMIN capability (container escape risk)", Severity.WARNING),
        ("SYS_PTRACE", "SYS_PTRACE capability (debugging risk)", Severity.WARNING),
        ("SYS_RAWIO", "SYS_RAWIO capability (raw I/O access)", Severity.WARNING),
        ("SYS_MODULE", "SYS_MODULE capability (kernel module loading)", Severity.CRITICAL),
        ("DAC_READ_SEARCH", "DAC_READ_SEARCH capability", Severity.WARNING),
    ]

    def scan_profile(self, data: dict[str, Any]) -> SecurityScanResult:
        """Scan a profile for security issues.

        Args:
            data: Profile data as dict

        Returns:
            SecurityScanResult with findings
        """
        findings: list[SecurityFinding] = []

        # Check setup_commands
        if "setup_commands" in data:
            findings.extend(
                self._check_commands(data["setup_commands"], "setup_commands")
            )

        # Check environment variables
        if "environment" in data:
            findings.extend(
                self._check_environment(data["environment"], "environment")
            )

        # Check volumes/mounts
        if "volumes" in data:
            findings.extend(self._check_volumes(data["volumes"], "volumes"))

        # Check capabilities
        if "capabilities_add" in data:
            findings.extend(
                self._check_capabilities(data["capabilities_add"], "capabilities_add")
            )

        # Check privileged mode
        if data.get("requires_privileged"):
            findings.append(
                SecurityFinding(
                    severity=Severity.WARNING,
                    category="privilege",
                    message="Profile requires privileged mode",
                    location="requires_privileged",
                )
            )

        return self._build_result(findings)

    def scan_spec(self, data: dict[str, Any]) -> SecurityScanResult:
        """Scan a spec for security issues.

        Args:
            data: Spec data as dict

        Returns:
            SecurityScanResult with findings
        """
        findings: list[SecurityFinding] = []

        # Check command
        if "command" in data:
            cmd = data["command"]
            if isinstance(cmd, list):
                cmd = " ".join(cmd)
            findings.extend(self._check_commands([cmd], "command"))

        # Check environment variables
        if "environment" in data:
            findings.extend(
                self._check_environment(data["environment"], "environment")
            )

        # Check mounts
        if "mounts" in data:
            findings.extend(self._check_mounts(data["mounts"], "mounts"))

        # Check volumes (older format)
        if "volumes" in data:
            findings.extend(self._check_volumes(data["volumes"], "volumes"))

        # Check security settings
        if "security" in data:
            security = data["security"]
            if security.get("privileged"):
                findings.append(
                    SecurityFinding(
                        severity=Severity.WARNING,
                        category="privilege",
                        message="Spec enables privileged mode",
                        location="security.privileged",
                    )
                )
            if "capabilities_add" in security:
                findings.extend(
                    self._check_capabilities(
                        security["capabilities_add"], "security.capabilities_add"
                    )
                )

        # Check top-level privileged
        if data.get("privileged"):
            findings.append(
                SecurityFinding(
                    severity=Severity.WARNING,
                    category="privilege",
                    message="Spec enables privileged mode",
                    location="privileged",
                )
            )

        return self._build_result(findings)

    def _check_commands(
        self, commands: list[str], location: str
    ) -> list[SecurityFinding]:
        """Check commands for suspicious patterns.

        Args:
            commands: List of commands to check
            location: Location in content for reporting

        Returns:
            List of security findings
        """
        findings: list[SecurityFinding] = []

        for i, cmd in enumerate(commands):
            if not isinstance(cmd, str):
                continue

            for pattern, description, severity in self.SUSPICIOUS_COMMANDS:
                if re.search(pattern, cmd, re.IGNORECASE):
                    findings.append(
                        SecurityFinding(
                            severity=severity,
                            category="command_injection",
                            message=f"{description}: {cmd[:50]}{'...' if len(cmd) > 50 else ''}",
                            location=f"{location}[{i}]",
                        )
                    )

        return findings

    def _check_environment(
        self, env: dict[str, str], location: str
    ) -> list[SecurityFinding]:
        """Check environment variables for credential exposure.

        Args:
            env: Environment variables dict
            location: Location in content for reporting

        Returns:
            List of security findings
        """
        findings: list[SecurityFinding] = []

        for key, value in env.items():
            # Check variable name
            for pattern, description in self.CREDENTIAL_PATTERNS:
                if re.search(pattern, key, re.IGNORECASE):
                    # Check if value looks like a real credential
                    if value and not value.startswith("${") and len(value) > 3:
                        findings.append(
                            SecurityFinding(
                                severity=Severity.WARNING,
                                category="credentials",
                                message=f"{description} may contain hardcoded credential",
                                location=f"{location}.{key}",
                            )
                        )
                    break

            # Check for credential-like values
            if value and isinstance(value, str):
                for pattern, description in self.CREDENTIAL_VALUE_PATTERNS:
                    if re.match(pattern, value):
                        findings.append(
                            SecurityFinding(
                                severity=Severity.WARNING,
                                category="credentials",
                                message=f"{description} detected in {key}",
                                location=f"{location}.{key}",
                            )
                        )
                        break

        return findings

    def _check_volumes(
        self, volumes: dict[str, str] | list[str], location: str
    ) -> list[SecurityFinding]:
        """Check volumes for sensitive path mounts.

        Args:
            volumes: Volumes as dict (host: container) or list
            location: Location in content for reporting

        Returns:
            List of security findings
        """
        findings: list[SecurityFinding] = []

        paths_to_check: list[str] = []

        if isinstance(volumes, dict):
            paths_to_check.extend(volumes.keys())
            paths_to_check.extend(volumes.values())
        elif isinstance(volumes, list):
            for vol in volumes:
                if isinstance(vol, str):
                    # Parse "host:container" or "host:container:ro"
                    parts = vol.split(":")
                    paths_to_check.extend(parts[:2])

        for path in paths_to_check:
            for pattern, description, severity in self.SENSITIVE_PATHS:
                if re.search(pattern, path, re.IGNORECASE):
                    findings.append(
                        SecurityFinding(
                            severity=severity,
                            category="sensitive_mount",
                            message=f"{description}: {path}",
                            location=location,
                        )
                    )
                    break

        return findings

    def _check_mounts(
        self, mounts: list[dict[str, Any]], location: str
    ) -> list[SecurityFinding]:
        """Check mounts for sensitive path mounts.

        Args:
            mounts: List of mount configurations
            location: Location in content for reporting

        Returns:
            List of security findings
        """
        findings: list[SecurityFinding] = []

        for i, mount in enumerate(mounts):
            if not isinstance(mount, dict):
                continue

            paths_to_check = []
            if "host" in mount:
                paths_to_check.append(mount["host"])
            if "container" in mount:
                paths_to_check.append(mount["container"])
            if "source" in mount:
                paths_to_check.append(mount["source"])
            if "target" in mount:
                paths_to_check.append(mount["target"])

            for path in paths_to_check:
                for pattern, description, severity in self.SENSITIVE_PATHS:
                    if re.search(pattern, str(path), re.IGNORECASE):
                        findings.append(
                            SecurityFinding(
                                severity=severity,
                                category="sensitive_mount",
                                message=f"{description}: {path}",
                                location=f"{location}[{i}]",
                            )
                        )
                        break

        return findings

    def _check_capabilities(
        self, capabilities: list[str], location: str
    ) -> list[SecurityFinding]:
        """Check capabilities for dangerous ones.

        Args:
            capabilities: List of capability names
            location: Location in content for reporting

        Returns:
            List of security findings
        """
        findings: list[SecurityFinding] = []

        for cap in capabilities:
            cap_upper = cap.upper()
            for dangerous_cap, description, severity in self.DANGEROUS_CAPABILITIES:
                if cap_upper == dangerous_cap:
                    findings.append(
                        SecurityFinding(
                            severity=severity,
                            category="dangerous_capability",
                            message=description,
                            location=f"{location}.{cap}",
                        )
                    )
                    break

        return findings

    def _build_result(self, findings: list[SecurityFinding]) -> SecurityScanResult:
        """Build SecurityScanResult from findings.

        Args:
            findings: List of security findings

        Returns:
            SecurityScanResult with appropriate risk level
        """
        if not findings:
            return SecurityScanResult(passed=True, risk_level="low", findings=[])

        # Determine risk level based on findings
        has_critical = any(f.severity == Severity.CRITICAL for f in findings)
        has_error = any(f.severity == Severity.ERROR for f in findings)
        has_warning = any(f.severity == Severity.WARNING for f in findings)

        if has_critical:
            risk_level = "critical"
            passed = False
        elif has_error:
            risk_level = "high"
            passed = False
        elif has_warning:
            risk_level = "medium"
            passed = True  # Warnings don't fail the scan
        else:
            risk_level = "low"
            passed = True

        return SecurityScanResult(
            passed=passed,
            risk_level=risk_level,
            findings=findings,
        )


# Module-level scanner instance
_scanner: SecurityScanner | None = None


def get_security_scanner() -> SecurityScanner:
    """Get the global security scanner instance.

    Returns:
        SecurityScanner instance
    """
    global _scanner
    if _scanner is None:
        _scanner = SecurityScanner()
    return _scanner
