"""RPC command handlers for device management.

This module implements the actual command handlers that execute
system operations in response to RPC requests.
"""

import logging
import platform
import subprocess
import sys
import time
from pathlib import Path
from typing import Any

try:
    import psutil
except ImportError:
    psutil = None  # type: ignore

try:
    import yaml
except ImportError:
    yaml = None  # type: ignore

# Add parent src to path for development
parent_src = Path(__file__).parent.parent.parent.parent.parent / "src"
if parent_src.exists():
    sys.path.insert(0, str(parent_src))

from styrened.protocols.base import LXMFMessage  # noqa: E402  # type: ignore[import-untyped]

logger = logging.getLogger(__name__)


async def handle_status(message: LXMFMessage) -> dict[str, Any]:
    """Handle status request - return system information.

    Args:
        message: Incoming status request message

    Returns:
        StatusResponse dict with system information
    """
    logger.debug("Handling status request")

    # Calculate uptime
    if psutil is not None:
        boot_time = psutil.boot_time()
        uptime = int(time.time() - boot_time)
    else:
        uptime = 0

    # Get IP address (simplified - get first non-loopback)
    ip = "unknown"
    try:
        import socket
        hostname = socket.gethostname()
        ip = socket.gethostbyname(hostname)
    except Exception:
        pass

    # Get running services (simplified - check common service processes)
    services: list[str] = []
    if psutil is not None:
        for proc in psutil.process_iter(['name']):
            try:
                name = proc.info['name']
                if name in ['reticulum', 'nomadnet', 'sshd', 'systemd']:
                    if name not in services:
                        services.append(name)
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                pass

    # Get disk usage
    disk_used = 0
    disk_total = 0
    if psutil is not None:
        try:
            disk = psutil.disk_usage('/')
            disk_used = disk.used
            disk_total = disk.total
        except Exception:
            pass

    return {
        "type": "status_response",
        "uptime": uptime,
        "ip": ip,
        "services": services,
        "disk_used": disk_used,
        "disk_total": disk_total,
    }


async def handle_exec(message: LXMFMessage) -> dict[str, Any]:
    """Handle exec request - execute shell command.

    Args:
        message: Incoming exec request message

    Returns:
        ExecResult dict with command output
    """
    fields = message.fields
    command = fields.get("command", "")
    args = fields.get("args", [])

    logger.debug(f"Handling exec request: {command} {args}")

    # Security: Only allow specific commands (whitelist)
    # For testing, we allow all commands, but in production this should be restricted
    allowed_commands = {
        "systemctl",
        "echo",
        "cat",
        "ls",
        "df",
        "uptime",
        "hostname",
        "date",
        "sleep",
        "false",
        "true",
    }

    if command not in allowed_commands:
        return {
            "type": "exec_result",
            "exit_code": -1,
            "stdout": "",
            "stderr": f"Command '{command}' not in whitelist",
        }

    try:
        # Execute command with timeout
        result = subprocess.run(
            [command] + args,
            capture_output=True,
            text=True,
            timeout=30.0,
            check=False,
        )

        return {
            "type": "exec_result",
            "exit_code": result.returncode,
            "stdout": result.stdout,
            "stderr": result.stderr,
        }

    except subprocess.TimeoutExpired:
        return {
            "type": "exec_result",
            "exit_code": -1,
            "stdout": "",
            "stderr": "Command timed out after 30 seconds",
        }

    except Exception as e:
        logger.error(f"Exec handler error: {e}")
        return {
            "type": "exec_result",
            "exit_code": -1,
            "stdout": "",
            "stderr": f"Execution error: {str(e)}",
        }


async def handle_reboot(message: LXMFMessage) -> dict[str, Any]:
    """Handle reboot request - schedule device reboot.

    Args:
        message: Incoming reboot request message

    Returns:
        RebootResult dict with success status
    """
    fields = message.fields
    delay = fields.get("delay", 0)

    logger.info(f"Handling reboot request with delay={delay}")

    # Determine platform-specific reboot command
    if platform.system() == "Linux":
        if delay == 0:
            cmd = ["shutdown", "-r", "now"]
        else:
            cmd = ["shutdown", "-r", f"+{delay // 60}"]  # Convert seconds to minutes
    elif platform.system() == "Darwin":
        if delay == 0:
            cmd = ["shutdown", "-r", "now"]
        else:
            # macOS doesn't support delayed shutdown easily, use 'at' command alternative
            cmd = ["shutdown", "-r", f"+{delay // 60}"]
    else:
        return {
            "type": "reboot_result",
            "success": False,
            "message": f"Reboot not supported on {platform.system()}",
            "scheduled_time": None,
        }

    try:
        # Execute reboot command
        # In testing, this will fail due to permissions, which is expected
        result = subprocess.run(cmd, capture_output=True, text=True, check=False)

        if result.returncode == 0:
            scheduled_time = time.time() + delay if delay > 0 else None
            return {
                "type": "reboot_result",
                "success": True,
                "message": f"Reboot scheduled (delay: {delay}s)",
                "scheduled_time": scheduled_time,
            }
        else:
            return {
                "type": "reboot_result",
                "success": False,
                "message": f"Reboot failed: {result.stderr}",
                "scheduled_time": None,
            }

    except Exception as e:
        logger.error(f"Reboot handler error: {e}")
        return {
            "type": "reboot_result",
            "success": False,
            "message": f"Reboot error: {str(e)}",
            "scheduled_time": None,
        }


def get_config_path() -> str:
    """Get path to device configuration file.

    Returns:
        Path to config file
    """
    # Default location
    config_dir = Path.home() / ".config" / "styrene-bond-rpc"
    config_dir.mkdir(parents=True, exist_ok=True)
    return str(config_dir / "device_config.yaml")


async def handle_update_config(message: LXMFMessage) -> dict[str, Any]:
    """Handle update config request - update device configuration.

    Args:
        message: Incoming update config request message

    Returns:
        UpdateConfigResult dict with updated keys
    """
    fields = message.fields
    config_updates = fields.get("config", {})

    logger.info(f"Handling update config request: {list(config_updates.keys())}")

    if yaml is None:
        return {
            "type": "update_config_result",
            "success": False,
            "message": "YAML library not available",
            "updated_keys": [],
        }

    try:
        config_path = Path(get_config_path())

        # Load existing config or create new
        if config_path.exists():
            try:
                with open(config_path, 'r') as f:
                    config = yaml.safe_load(f) or {}
            except yaml.YAMLError:
                # Invalid YAML, create new config
                logger.warning(f"Invalid YAML in {config_path}, creating new config")
                config = {}
        else:
            config = {}

        # Update config with new values
        updated_keys = []
        for key, value in config_updates.items():
            config[key] = value
            updated_keys.append(key)

        # Write updated config
        with open(config_path, 'w') as f:
            yaml.safe_dump(config, f, default_flow_style=False)

        return {
            "type": "update_config_result",
            "success": True,
            "message": f"Updated {len(updated_keys)} config keys",
            "updated_keys": updated_keys,
        }

    except Exception as e:
        logger.error(f"Update config handler error: {e}")
        return {
            "type": "update_config_result",
            "success": False,
            "message": f"Config update error: {str(e)}",
            "updated_keys": [],
        }
