#!/usr/bin/env python3
"""
Claude Buddy - Command Validation Hook

This PreToolUse hook validates and blocks dangerous bash commands before execution.
It helps prevent accidental system damage and promotes safer development practices.
"""

import sys
import json
import os
import re
from typing import List, Dict, Any, Tuple

# Dangerous command patterns that should be blocked
DANGEROUS_COMMANDS = [
    # Destructive file operations
    (r"rm\s+.*-rf?\s+/", "Recursive deletion from root directory"),
    (r"rm\s+.*-rf?\s+\*", "Recursive deletion with wildcards"),
    (r"rm\s+.*-rf?\s+~", "Recursive deletion from home directory"),
    (r">\s*/dev/sd[a-z]", "Direct writes to disk devices"),
    
    # System modification
    (r"sudo\s+rm\s+.*-rf?", "Sudo recursive deletion"),
    (r"chmod\s+777\s+/", "Overly permissive root permissions"),
    (r"chown\s+.*:\s*/", "Ownership changes to root directory"),
    
    # Network and system access
    (r"nc\s+.*-e", "Netcat with command execution"),
    (r"curl\s+.*\|\s*sh", "Downloading and executing scripts"),
    (r"wget\s+.*\|\s*sh", "Downloading and executing scripts"),
    (r"bash\s+<\(curl", "Bash execution from remote scripts"),
    
    # System formatting and partitioning
    (r"fdisk\s+/dev/", "Disk partitioning operations"),
    (r"mkfs\.", "Filesystem creation"),
    (r"dd\s+.*of=/dev/", "Direct disk writes"),
    
    # Process manipulation
    (r"kill\s+-9\s+1", "Killing init process"),
    (r"killall\s+-9", "Forceful termination of all processes"),
    
    # System configuration
    (r"echo\s+.*>\s*/etc/", "Writing to system configuration"),
    (r">\s*/etc/passwd", "Modifying user accounts"),
    (r">\s*/etc/shadow", "Modifying password file"),
]

# Performance anti-patterns to warn about
PERFORMANCE_WARNINGS = [
    (r"find\s+.*-name", "Consider using 'rg --files -g pattern' for better performance"),
    (r"grep\s+(?!.*\|)", "Consider using 'rg' (ripgrep) for faster searching"),
    (r"cat\s+.*\|\s*grep", "Consider using 'rg pattern file' instead of 'cat file | grep pattern'"),
    (r"ls\s+.*\|\s*grep", "Consider using shell globbing or 'rg --files' instead"),
]

# Best practice suggestions
BEST_PRACTICE_SUGGESTIONS = [
    (r"sudo\s+(?!apt|yum|brew)", "Consider if sudo is really necessary for this operation"),
    (r"chmod\s+\d{3,4}", "Consider using symbolic permissions (e.g., 'chmod u+x') for clarity"),
    (r"git\s+push\s+.*--force", "Consider using '--force-with-lease' instead of '--force' for safer pushing"),
]

def load_config() -> Dict[str, Any]:
    """Load Claude Buddy configuration for custom command rules."""
    # Try new location first: .claude/hooks.json
    hooks_config_paths = [
        ".claude/hooks.json",
        os.path.expanduser("~/.claude/hooks.json")
    ]

    for config_path in hooks_config_paths:
        if os.path.exists(config_path):
            try:
                with open(config_path, 'r') as f:
                    hooks_data = json.load(f)
                    # Extract config section from hooks.json
                    if "config" in hooks_data:
                        return hooks_data["config"]
            except (json.JSONDecodeError, IOError):
                continue

    # Return defaults if no config found
    return {
        "command_validation": {
            "enabled": True,
            "block_dangerous": True,
            "warn_performance": True,
            "suggest_best_practices": True,
            "additional_dangerous_patterns": [],
            "whitelist_patterns": [],
            "strict_mode": False
        }
    }

def check_dangerous_commands(command: str, config: Dict[str, Any]) -> Tuple[bool, str]:
    """Check if command contains dangerous patterns."""
    validation_config = config.get("command_validation", {})
    
    if not validation_config.get("block_dangerous", True):
        return False, ""
    
    # Check whitelist first
    whitelist_patterns = validation_config.get("whitelist_patterns", [])
    for pattern in whitelist_patterns:
        if re.search(pattern, command, re.IGNORECASE):
            return False, ""  # Whitelisted command
    
    # Combine default and additional dangerous patterns
    dangerous_patterns = DANGEROUS_COMMANDS.copy()
    additional_patterns = validation_config.get("additional_dangerous_patterns", [])
    for pattern in additional_patterns:
        dangerous_patterns.append((pattern, "Custom dangerous pattern"))
    
    # Check each dangerous pattern
    for pattern, description in dangerous_patterns:
        if re.search(pattern, command, re.IGNORECASE):
            return True, description
    
    return False, ""

def check_performance_issues(command: str, config: Dict[str, Any]) -> List[str]:
    """Check for performance anti-patterns and return suggestions."""
    validation_config = config.get("command_validation", {})
    
    if not validation_config.get("warn_performance", True):
        return []
    
    suggestions = []
    for pattern, suggestion in PERFORMANCE_WARNINGS:
        if re.search(pattern, command, re.IGNORECASE):
            suggestions.append(suggestion)
    
    return suggestions

def check_best_practices(command: str, config: Dict[str, Any]) -> List[str]:
    """Check for best practice violations and return suggestions."""
    validation_config = config.get("command_validation", {})
    
    if not validation_config.get("suggest_best_practices", True):
        return []
    
    suggestions = []
    for pattern, suggestion in BEST_PRACTICE_SUGGESTIONS:
        if re.search(pattern, command, re.IGNORECASE):
            suggestions.append(suggestion)
    
    return suggestions

def get_safer_alternative(command: str) -> str:
    """Suggest safer alternatives for dangerous commands."""
    command_lower = command.lower()
    
    if "rm -rf" in command_lower:
        return "Consider using 'trash' command or move files to a backup location first"
    elif "chmod 777" in command_lower:
        return "Use more specific permissions like 'chmod u+rwx,g+r,o+r' instead"
    elif "curl" in command_lower and "|" in command_lower and "sh" in command_lower:
        return "Download the script first, review it, then execute: wget script.sh && cat script.sh && bash script.sh"
    elif "sudo" in command_lower and "rm" in command_lower:
        return "Double-check the path and consider using a non-destructive approach first"
    else:
        return "Review the command carefully and consider if there's a safer approach"

def create_block_response(command: str, reason: str, alternative: str) -> Dict[str, Any]:
    """Create a JSON response to block the command execution."""
    return {
        "decision": "block",
        "reason": f"""🚫 Dangerous Command Blocked: '{command}'

⚠️ Risk: {reason}

🛡️ Claude Buddy blocked this command to protect your system from potential damage.

💡 Safer approach: {alternative}

If you're certain this command is safe:
1. Run it directly in your terminal
2. Add to whitelist in .claude/hooks.json (config.command_validation.whitelist_patterns)
3. Disable validation by setting config.command_validation.block_dangerous = false

Stay safe! 🔒""",
        "continue": False,
        "suppressOutput": False
    }

def create_warning_response(command: str, warnings: List[str], suggestions: List[str]) -> Dict[str, Any]:
    """Create a JSON response with warnings but allow execution."""
    message_parts = [f"⚠️ Command Analysis: '{command}'"]
    
    if warnings:
        message_parts.append("\n🐌 Performance Suggestions:")
        for warning in warnings:
            message_parts.append(f"  • {warning}")
    
    if suggestions:
        message_parts.append("\n💡 Best Practice Suggestions:")
        for suggestion in suggestions:
            message_parts.append(f"  • {suggestion}")
    
    message_parts.append("\nCommand will proceed, but consider the suggestions above.")
    
    return {
        "decision": "approve",
        "reason": "\n".join(message_parts),
        "continue": True,
        "suppressOutput": False
    }

def create_approve_response() -> Dict[str, Any]:
    """Create a JSON response to approve the command execution."""
    return {
        "decision": "approve",
        "continue": True,
        "suppressOutput": True
    }

def log_command_event(command: str, action: str, blocked: bool, warnings: List[str], config: Dict[str, Any]):
    """Log command validation events for audit trail."""
    logging_config = config.get("logging", {})

    # Check if logging is enabled
    if not logging_config.get("enabled", True):
        return

    # Check if command executions should be logged
    if not logging_config.get("command_executions", True):
        return

    log_dir = ".claude/logs"
    if not os.path.exists(log_dir):
        try:
            os.makedirs(log_dir)
        except OSError:
            return

    log_file = os.path.join(log_dir, "commands.log")

    import datetime
    timestamp = datetime.datetime.now().isoformat()

    log_level = logging_config.get("level", "info")

    log_entry = {
        "timestamp": timestamp,
        "level": "warn" if blocked else "info",
        "command": command,
        "action": action,
        "blocked": blocked,
        "warnings": warnings,
        "tool": "command-validator"
    }

    # Only log if level is appropriate
    if log_level == "error" and not blocked:
        return  # Only log blocks in error mode

    try:
        with open(log_file, "a") as f:
            f.write(json.dumps(log_entry) + "\n")
    except IOError:
        pass

def send_notification(title: str, message: str, config: Dict[str, Any]):
    """Send desktop notification if enabled."""
    notifications_config = config.get("notifications", {})

    # Check if notifications are enabled
    if not notifications_config.get("desktop_alerts", False):
        return

    # Check if protection events should trigger notifications
    if not notifications_config.get("protection_events", True):
        return

    try:
        import platform
        system = platform.system()

        if system == "Darwin":  # macOS
            # Use osascript for macOS notifications
            import subprocess
            script = f'display notification "{message}" with title "{title}"'
            subprocess.run(["osascript", "-e", script], capture_output=True, timeout=2)
        elif system == "Linux":
            # Use notify-send for Linux
            import subprocess
            subprocess.run(["notify-send", title, message], capture_output=True, timeout=2)
        # Windows notifications could be added here if needed
    except Exception:
        pass  # Notification failed, but don't block the operation

def main():
    """Main hook execution function."""
    try:
        # Read JSON input from stdin
        input_data = json.load(sys.stdin)
    except json.JSONDecodeError as e:
        print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
        sys.exit(1)
    
    # Extract tool information
    tool_name = input_data.get("tool_name", "")
    tool_input = input_data.get("tool_input", {})
    
    # Only process Bash tool
    if tool_name != "Bash":
        print(json.dumps(create_approve_response()))
        sys.exit(0)
    
    # Get command being executed
    command = tool_input.get("command", "")
    
    if not command.strip():
        print(json.dumps(create_approve_response()))
        sys.exit(0)
    
    # Load configuration
    config = load_config()

    # Check if safety hooks feature is enabled (master switch)
    if not config.get("features", {}).get("safety_hooks", True):
        print(json.dumps(create_approve_response()))
        sys.exit(0)

    # Check if command validation is enabled
    if not config.get("command_validation", {}).get("enabled", True):
        print(json.dumps(create_approve_response()))
        sys.exit(0)
    
    # Check for dangerous commands
    is_dangerous, danger_reason = check_dangerous_commands(command, config)
    
    if is_dangerous:
        alternative = get_safer_alternative(command)
        log_command_event(command, "blocked", True, [danger_reason], config)

        # Send notification if enabled
        send_notification(
            "Dangerous Command Blocked",
            f"Blocked: {command[:50]}{'...' if len(command) > 50 else ''}",
            config
        )

        response = create_block_response(command, danger_reason, alternative)
        print(json.dumps(response))
        sys.exit(2)  # Exit code 2 indicates blocking

    # Check for performance issues and best practice violations
    performance_warnings = check_performance_issues(command, config)
    best_practice_suggestions = check_best_practices(command, config)

    all_warnings = performance_warnings + best_practice_suggestions

    if all_warnings:
        log_command_event(command, "warned", False, all_warnings, config)
        response = create_warning_response(command, performance_warnings, best_practice_suggestions)
        print(json.dumps(response))
        sys.exit(0)

    # Command is safe, log and approve
    log_command_event(command, "approved", False, [], config)
    print(json.dumps(create_approve_response()))
    sys.exit(0)

if __name__ == "__main__":
    main()