"""Safety guard system for OCLAWMA.

This module provides safety mechanisms to detect and prevent risky operations,
including:
- Risky operation detection (rm -rf, external API calls, etc.)
- Confirmation prompts for destructive actions
- Dry-run mode simulation
- Audit logging of all actions
"""

from __future__ import annotations

import hashlib
import json
import re
import secrets
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any


class RiskLevel(Enum):
    """Risk level classification for operations."""

    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


@dataclass
class RiskyOperation:
    """Represents a detected risky operation."""

    operation_type: str
    risk_level: RiskLevel
    description: str
    details: dict[str, Any] = field(default_factory=dict)
    mitigation: str | None = None


@dataclass
class AuditEntry:
    """Single audit log entry."""

    timestamp: datetime
    tool_name: str
    operation: str
    parameters: dict[str, Any]
    risk_level: RiskLevel
    was_blocked: bool
    was_dry_run: bool
    user_confirmed: bool | None = None
    result_hash: str | None = None
    session_id: str | None = None


class SafetyGuard:
    """Safety guard for detecting and preventing risky operations.

    This class provides:
    1. Pattern-based detection of risky shell commands and operations
    2. Confirmation prompts for destructive actions
    3. Dry-run mode simulation
    4. Comprehensive audit logging
    """

    # Risky shell command patterns
    RISKY_PATTERNS: dict[RiskLevel, list[str]] = {
        RiskLevel.CRITICAL: [
            r"rm\s+-[a-zA-Z]*f.*\s+/",  # rm -rf / or rm -f /
            r"rm\s+-[a-zA-Z]*f.*\s+~/\.\.",  # rm -rf ~/.. (home parent)
            r">\s*/\S+",  # Overwriting root files
            r":\(\)\s*\{\s*:\|:&\s*\};\s*:",  # Fork bomb
            r"mkfs\.",  # Filesystem formatting
            r"dd\s+.*of=/dev/[sh]d[a-z]",  # Direct disk writing
            r"mv\s+.*\s+/dev/null",  # Moving to null
        ],
        RiskLevel.HIGH: [
            r"rm\s+-[a-zA-Z]*r[a-zA-Z]*f",  # rm -rf anywhere
            r"rm\s+-[a-zA-Z]*f",  # rm -f (force delete)
            r"chmod\s+-[rR]",  # Recursive chmod
            r"chown\s+-[rR]",  # Recursive chown
            r"find\s+.*\s+-delete",  # Find with delete
            r"find\s+.*\s+-exec\s+rm",  # Find exec rm
            r"\|\s*sh",  # Piping to shell
            r"\|\s*bash",  # Piping to bash
            r"curl\s+.*\|\s*(sh|bash)",  # Curl pipe to shell
            r"wget\s+.*\|\s*(sh|bash)",  # Wget pipe to shell
            r"eval\s*\$",  # Eval on variables
        ],
        RiskLevel.MEDIUM: [
            r"rm\s+-[a-zA-Z]*r",  # rm -r (recursive delete)
            r"\bsudo\s+rm",  # sudo rm
            r"\bsudo\s+chmod",  # sudo chmod
            r"\bsudo\s+chown",  # sudo chown
            r"git\s+clean\s+-[a-zA-Z]*f",  # git clean -f
            r"git\s+reset\s+--hard",  # git reset --hard
            r"git\s+push\s+.*--force",  # git push --force
            r"docker\s+rm\s+-[a-zA-Z]*f",  # docker rm -f
            r"docker\s+system\s+prune",  # docker system prune
            r"kubectl\s+delete",  # kubectl delete
        ],
    }

    # External API patterns that might incur costs
    API_PATTERNS: list[str] = [
        r"curl\s+.*api\.",
        r"curl\s+.*openai\.",
        r"curl\s+.*anthropic\.",
        r"curl\s+.*kimi\.",
        r"wget\s+.*api\.",
        r"http[s]?://[^\s]*api[^\s]*",
    ]

    def __init__(
        self,
        dry_run: bool = False,
        require_confirmation: bool = True,
        audit_log_path: Path | None = None,
        session_id: str | None = None,
    ) -> None:
        """Initialize the safety guard.

        Args:
            dry_run: If True, operations are simulated but not executed
            require_confirmation: If True, risky operations require user confirmation
            audit_log_path: Path to audit log file (default: ~/.oclawma/audit.log)
            session_id: Unique session identifier for audit logs
        """
        self.dry_run = dry_run
        self.require_confirmation = require_confirmation
        self.session_id = session_id or self._generate_session_id()

        if audit_log_path is None:
            audit_dir = Path.home() / ".oclawma"
            audit_dir.mkdir(parents=True, exist_ok=True)
            self.audit_log_path = audit_dir / "audit.log"
        else:
            self.audit_log_path = audit_log_path
            self.audit_log_path.parent.mkdir(parents=True, exist_ok=True)

        # Track confirmed operations in this session (to avoid re-confirming)
        self._confirmed_operations: set[str] = set()

    def _generate_session_id(self) -> str:
        """Generate a unique session ID."""
        return secrets.token_hex(8)

    def analyze_command(self, command: str) -> list[RiskyOperation]:
        """Analyze a shell command for risky operations.

        Args:
            command: The shell command to analyze

        Returns:
            List of detected risky operations (empty if safe)
        """
        risks: list[RiskyOperation] = []
        command_lower = command.lower()

        # Check critical patterns first
        for pattern in self.RISKY_PATTERNS[RiskLevel.CRITICAL]:
            if re.search(pattern, command_lower):
                risks.append(
                    RiskyOperation(
                        operation_type="destructive_command",
                        risk_level=RiskLevel.CRITICAL,
                        description=f"Critical destructive command detected: pattern match '{pattern}'",
                        details={"pattern": pattern, "command": command},
                        mitigation="This command could cause system damage. Verify carefully before proceeding.",
                    )
                )

        # Check high risk patterns
        for pattern in self.RISKY_PATTERNS[RiskLevel.HIGH]:
            if re.search(pattern, command_lower):
                risks.append(
                    RiskyOperation(
                        operation_type="high_risk_command",
                        risk_level=RiskLevel.HIGH,
                        description=f"High risk command detected: pattern match '{pattern}'",
                        details={"pattern": pattern, "command": command},
                        mitigation="This command may delete files or execute remote code. Review carefully.",
                    )
                )

        # Check medium risk patterns
        for pattern in self.RISKY_PATTERNS[RiskLevel.MEDIUM]:
            if re.search(pattern, command_lower):
                risks.append(
                    RiskyOperation(
                        operation_type="medium_risk_command",
                        risk_level=RiskLevel.MEDIUM,
                        description=f"Medium risk command detected: pattern match '{pattern}'",
                        details={"pattern": pattern, "command": command},
                        mitigation="This command could have unintended consequences. Review before proceeding.",
                    )
                )

        # Check for external API calls
        for pattern in self.API_PATTERNS:
            if re.search(pattern, command_lower):
                risks.append(
                    RiskyOperation(
                        operation_type="external_api_call",
                        risk_level=RiskLevel.MEDIUM,
                        description="External API call detected",
                        details={"pattern": pattern, "command": command},
                        mitigation="This command calls an external API which may incur costs or transmit data.",
                    )
                )

        return risks

    def get_max_risk_level(self, risks: list[RiskyOperation]) -> RiskLevel:
        """Get the maximum risk level from a list of risks.

        Args:
            risks: List of risky operations

        Returns:
            Highest risk level (CRITICAL > HIGH > MEDIUM > LOW)
        """
        if not risks:
            return RiskLevel.LOW

        priority = {
            RiskLevel.CRITICAL: 4,
            RiskLevel.HIGH: 3,
            RiskLevel.MEDIUM: 2,
            RiskLevel.LOW: 1,
        }

        return max(risks, key=lambda r: priority.get(r.risk_level, 0)).risk_level

    def should_require_confirmation(self, risks: list[RiskyOperation]) -> bool:
        """Determine if confirmation should be required based on risks.

        Args:
            risks: List of detected risky operations

        Returns:
            True if user confirmation should be required
        """
        if not self.require_confirmation:
            return False

        if not risks:
            return False

        max_risk = self.get_max_risk_level(risks)
        return max_risk in (RiskLevel.HIGH, RiskLevel.CRITICAL)

    def format_risk_warning(self, risks: list[RiskyOperation], command: str) -> str:
        """Format a warning message for risky operations.

        Args:
            risks: List of risky operations
            command: The command being executed

        Returns:
            Formatted warning message
        """
        max_risk = self.get_max_risk_level(risks)

        lines = [
            "",
            "⚠️  SAFETY WARNING",
            "=" * 60,
            f"Command: {command}",
            f"Risk Level: {max_risk.value.upper()}",
            "",
            "Detected risks:",
        ]

        for i, risk in enumerate(risks, 1):
            lines.append(f"  {i}. [{risk.risk_level.value.upper()}] {risk.description}")
            if risk.mitigation:
                lines.append(f"     💡 {risk.mitigation}")

        lines.extend(
            [
                "",
                "=" * 60,
            ]
        )

        if self.dry_run:
            lines.append("📋 DRY RUN MODE: Command will be simulated but not executed.")

        return "\n".join(lines)

    def _hash_result(self, result: Any) -> str:
        """Create a hash of the result for audit logging."""
        try:
            result_str = json.dumps(result, default=str, sort_keys=True)
            return hashlib.sha256(result_str.encode()).hexdigest()[:16]
        except Exception:
            return "hash_error"

    def log_audit(
        self,
        tool_name: str,
        operation: str,
        parameters: dict[str, Any],
        risk_level: RiskLevel,
        was_blocked: bool,
        was_dry_run: bool,
        user_confirmed: bool | None = None,
        result: Any = None,
    ) -> None:
        """Write an entry to the audit log.

        Args:
            tool_name: Name of the tool being used
            operation: Description of the operation
            parameters: Operation parameters (sensitive data should be redacted)
            risk_level: Risk level of the operation
            was_blocked: Whether the operation was blocked
            was_dry_run: Whether this was a dry run
            user_confirmed: Whether the user confirmed (if applicable)
            result: Operation result for hashing
        """
        entry = AuditEntry(
            timestamp=datetime.now(),
            tool_name=tool_name,
            operation=operation,
            parameters=self._sanitize_parameters(parameters),
            risk_level=risk_level,
            was_blocked=was_blocked,
            was_dry_run=was_dry_run,
            user_confirmed=user_confirmed,
            result_hash=self._hash_result(result) if result else None,
            session_id=self.session_id,
        )

        # Write to audit log
        try:
            with open(self.audit_log_path, "a") as f:
                log_line = json.dumps(
                    {
                        "timestamp": entry.timestamp.isoformat(),
                        "session_id": entry.session_id,
                        "tool_name": entry.tool_name,
                        "operation": entry.operation,
                        "parameters": entry.parameters,
                        "risk_level": entry.risk_level.value,
                        "was_blocked": entry.was_blocked,
                        "was_dry_run": entry.was_dry_run,
                        "user_confirmed": entry.user_confirmed,
                        "result_hash": entry.result_hash,
                    },
                    default=str,
                )
                f.write(log_line + "\n")
        except Exception:
            # Fail silently - audit logging shouldn't block operations
            pass

    def _sanitize_parameters(self, parameters: dict[str, Any]) -> dict[str, Any]:
        """Sanitize parameters for audit logging (remove sensitive data).

        Args:
            parameters: Original parameters

        Returns:
            Sanitized parameters
        """
        sensitive_keys = {
            "password",
            "secret",
            "token",
            "key",
            "api_key",
            "auth",
            "credential",
            "passwd",
            "pwd",
        }

        sanitized = {}
        for key, value in parameters.items():
            key_lower = key.lower()
            if any(sensitive in key_lower for sensitive in sensitive_keys):
                sanitized[key] = "***REDACTED***"
            elif isinstance(value, dict):
                sanitized[key] = self._sanitize_parameters(value)
            else:
                sanitized[key] = value

        return sanitized

    def get_audit_log(self, limit: int = 100) -> list[AuditEntry]:
        """Read entries from the audit log.

        Args:
            limit: Maximum number of entries to return (most recent first)

        Returns:
            List of audit entries
        """
        entries: list[AuditEntry] = []

        if not self.audit_log_path.exists():
            return entries

        try:
            with open(self.audit_log_path) as f:
                lines = f.readlines()

            # Parse most recent entries first
            for line in reversed(lines[-limit:]):
                line = line.strip()
                if not line:
                    continue

                try:
                    data = json.loads(line)
                    entries.append(
                        AuditEntry(
                            timestamp=datetime.fromisoformat(data["timestamp"]),
                            tool_name=data["tool_name"],
                            operation=data["operation"],
                            parameters=data.get("parameters", {}),
                            risk_level=RiskLevel(data["risk_level"]),
                            was_blocked=data["was_blocked"],
                            was_dry_run=data["was_dry_run"],
                            user_confirmed=data.get("user_confirmed"),
                            result_hash=data.get("result_hash"),
                            session_id=data.get("session_id"),
                        )
                    )
                except (json.JSONDecodeError, KeyError, ValueError):
                    continue

        except Exception:
            pass

        return entries

    def clear_audit_log(self) -> bool:
        """Clear the audit log file.

        Returns:
            True if cleared successfully, False otherwise
        """
        try:
            if self.audit_log_path.exists():
                self.audit_log_path.unlink()
            return True
        except Exception:
            return False


# Convenience function for quick analysis
def analyze_command_safety(command: str) -> list[RiskyOperation]:
    """Quick analysis of a command for risky operations.

    Args:
        command: Shell command to analyze

    Returns:
        List of detected risks
    """
    guard = SafetyGuard()
    return guard.analyze_command(command)
