"""Security validation for backend plugins.

This module provides basic security checks for plugin backends to help
users identify potentially unsafe plugins before they execute.

Note: These are basic heuristics, not comprehensive security. Users should
only install plugins from trusted sources.
"""

import ast
import importlib.util
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


@dataclass
class SecurityCheck:
    """Result of a security check."""

    name: str
    passed: bool
    severity: str  # "info", "warning", "error"
    message: str


@dataclass
class SecurityReport:
    """Security validation report for a plugin."""

    plugin_path: Path
    checks: list[SecurityCheck]
    safe: bool
    warnings: list[str]
    errors: list[str]

    @property
    def has_warnings(self) -> bool:
        """Check if report has warnings."""
        return len(self.warnings) > 0

    @property
    def has_errors(self) -> bool:
        """Check if report has errors."""
        return len(self.errors) > 0


class PluginValidator:
    """Validates plugin security."""

    # Dangerous imports that should raise warnings
    DANGEROUS_IMPORTS = {
        "os.system",
        "subprocess",
        "eval",
        "exec",
        "__import__",
        "compile",
        "shutil.rmtree",
        "os.remove",
        "os.rmdir",
    }

    # File operations that should be checked
    FILE_OPERATIONS = {
        "open",
        "Path.write_text",
        "Path.write_bytes",
        "Path.unlink",
        "Path.rmdir",
    }

    # Network operations
    NETWORK_OPERATIONS = {
        "socket",
        "urllib.request",
        "requests",
        "httpx",
    }

    def __init__(self):
        """Initialize validator."""
        self.checks: list[SecurityCheck] = []

    def validate_plugin(self, plugin_path: Path) -> SecurityReport:
        """Validate a plugin's security.

        Args:
            plugin_path: Path to the plugin file.

        Returns:
            SecurityReport with validation results.
        """
        self.checks = []

        # Read plugin source
        try:
            source = plugin_path.read_text()
        except Exception as e:
            return self._error_report(
                plugin_path, f"Could not read plugin file: {e}"
            )

        # Parse AST
        try:
            tree = ast.parse(source, str(plugin_path))
        except SyntaxError as e:
            return self._error_report(
                plugin_path, f"Plugin has syntax errors: {e}"
            )

        # Run checks
        self._check_dangerous_imports(tree)
        self._check_file_operations(tree, plugin_path)
        self._check_network_access(tree)
        self._check_metadata(tree)

        # Compile report
        warnings = [c.message for c in self.checks if c.severity == "warning"]
        errors = [c.message for c in self.checks if c.severity == "error"]
        safe = len(errors) == 0

        return SecurityReport(
            plugin_path=plugin_path,
            checks=self.checks,
            safe=safe,
            warnings=warnings,
            errors=errors,
        )

    def _error_report(self, plugin_path: Path, error: str) -> SecurityReport:
        """Create an error report."""
        return SecurityReport(
            plugin_path=plugin_path,
            checks=[],
            safe=False,
            warnings=[],
            errors=[error],
        )

    def _check_dangerous_imports(self, tree: ast.AST) -> None:
        """Check for dangerous imports."""
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    if any(
                        danger in alias.name
                        for danger in ["subprocess", "eval", "exec"]
                    ):
                        self.checks.append(
                            SecurityCheck(
                                name="dangerous_import",
                                passed=False,
                                severity="warning",
                                message=f"Plugin imports {alias.name} which can execute arbitrary code",
                            )
                        )

            elif isinstance(node, ast.ImportFrom):
                module = node.module or ""
                for alias in node.names:
                    full_name = f"{module}.{alias.name}"
                    if any(danger in full_name for danger in self.DANGEROUS_IMPORTS):
                        self.checks.append(
                            SecurityCheck(
                                name="dangerous_import",
                                passed=False,
                                severity="warning",
                                message=f"Plugin imports {full_name} which may be unsafe",
                            )
                        )

            elif isinstance(node, (ast.Call)):
                # Check for eval/exec calls
                if isinstance(node.func, ast.Name):
                    if node.func.id in ["eval", "exec", "compile", "__import__"]:
                        self.checks.append(
                            SecurityCheck(
                                name="dangerous_call",
                                passed=False,
                                severity="error",
                                message=f"Plugin uses {node.func.id}() which can execute arbitrary code",
                            )
                        )

    def _check_file_operations(self, tree: ast.AST, plugin_path: Path) -> None:
        """Check file operations are limited to plugin directory."""
        has_file_ops = False

        for node in ast.walk(tree):
            if isinstance(node, ast.Call):
                # Check for file operations
                if isinstance(node.func, ast.Name) and node.func.id in [
                    "open",
                    "unlink",
                ]:
                    has_file_ops = True

                # Check for Path methods
                elif isinstance(node.func, ast.Attribute):
                    if node.func.attr in [
                        "write_text",
                        "write_bytes",
                        "unlink",
                        "rmdir",
                    ]:
                        has_file_ops = True

        if has_file_ops:
            self.checks.append(
                SecurityCheck(
                    name="file_operations",
                    passed=True,
                    severity="info",
                    message="Plugin performs file operations. Ensure it only accesses ~/.cleave/",
                )
            )

    def _check_network_access(self, tree: ast.AST) -> None:
        """Check for network operations."""
        has_network = False

        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    if any(
                        net in alias.name
                        for net in ["socket", "urllib", "requests", "httpx", "aiohttp"]
                    ):
                        has_network = True

            elif isinstance(node, ast.ImportFrom):
                module = node.module or ""
                if any(
                    net in module
                    for net in ["socket", "urllib", "requests", "httpx", "aiohttp"]
                ):
                    has_network = True

        if has_network:
            self.checks.append(
                SecurityCheck(
                    name="network_access",
                    passed=True,
                    severity="info",
                    message="Plugin makes network requests. Ensure you trust the endpoints.",
                )
            )

    def _check_metadata(self, tree: ast.AST) -> None:
        """Check plugin has required metadata."""
        metadata = {
            "__plugin_name__": False,
            "__plugin_version__": False,
            "__plugin_backend__": False,
        }

        for node in ast.walk(tree):
            if isinstance(node, ast.Assign):
                for target in node.targets:
                    if isinstance(target, ast.Name) and target.id in metadata:
                        metadata[target.id] = True

        for key, found in metadata.items():
            if not found:
                self.checks.append(
                    SecurityCheck(
                        name="metadata",
                        passed=False,
                        severity="error",
                        message=f"Plugin missing required metadata: {key}",
                    )
                )


def validate_plugin(plugin_path: Path) -> SecurityReport:
    """Validate a plugin's security.

    Args:
        plugin_path: Path to the plugin file.

    Returns:
        SecurityReport with validation results.
    """
    validator = PluginValidator()
    return validator.validate_plugin(plugin_path)


def print_security_report(report: SecurityReport) -> None:
    """Print a security report to console.

    Args:
        report: Security report to print.
    """
    print(f"\nSecurity Report: {report.plugin_path.name}")
    print("=" * 60)

    if report.safe:
        print("✓ No critical security issues found")
    else:
        print("✗ Security concerns detected")

    if report.errors:
        print("\nERRORS:")
        for error in report.errors:
            print(f"  ✗ {error}")

    if report.warnings:
        print("\nWARNINGS:")
        for warning in report.warnings:
            print(f"  ! {warning}")

    # Print info checks
    info_checks = [c for c in report.checks if c.severity == "info"]
    if info_checks:
        print("\nINFO:")
        for check in info_checks:
            print(f"  ℹ {check.message}")

    print()


def scan_plugin_directory(plugin_dir: Path) -> dict[str, SecurityReport]:
    """Scan all plugins in a directory.

    Args:
        plugin_dir: Directory containing plugins.

    Returns:
        Dictionary mapping plugin names to security reports.
    """
    reports = {}

    if not plugin_dir.exists():
        return reports

    for plugin_file in plugin_dir.glob("*.py"):
        if plugin_file.name.startswith("__"):
            continue

        report = validate_plugin(plugin_file)
        reports[plugin_file.stem] = report

    return reports


__all__ = [
    "SecurityCheck",
    "SecurityReport",
    "PluginValidator",
    "validate_plugin",
    "print_security_report",
    "scan_plugin_directory",
]
