"""Tests for RPC command handlers."""

import pytest
from unittest.mock import Mock, patch, AsyncMock
import time

from styrene_bond_rpc.handlers import (
    handle_status,
    handle_exec,
    handle_reboot,
    handle_update_config,
)
from styrened.protocols.base import LXMFMessage


class TestStatusHandler:
    """Test status command handler."""

    @pytest.mark.asyncio
    async def test_handle_status_returns_system_info(self) -> None:
        """Status handler should return system information."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={"protocol": "rpc", "type": "status_request"},
        )

        with patch("psutil.boot_time", return_value=time.time() - 12345):
            with patch("psutil.disk_usage") as mock_disk:
                mock_disk.return_value = Mock(used=4200000000, total=28000000000)

                result = await handle_status(msg)

        assert result["type"] == "status_response"
        assert "uptime" in result
        assert "ip" in result
        assert "services" in result
        assert "disk_used" in result
        assert "disk_total" in result

    @pytest.mark.asyncio
    async def test_handle_status_uptime_calculation(self) -> None:
        """Status handler should calculate uptime correctly."""
        current_time = time.time()
        boot_time = current_time - 3600  # 1 hour uptime

        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={"protocol": "rpc", "type": "status_request"},
        )

        with patch("psutil.boot_time", return_value=boot_time):
            with patch("psutil.disk_usage") as mock_disk:
                mock_disk.return_value = Mock(used=1000000, total=10000000)
                with patch("time.time", return_value=current_time):
                    result = await handle_status(msg)

        # Uptime should be approximately 3600 seconds
        assert 3590 <= result["uptime"] <= 3610


class TestExecHandler:
    """Test exec command handler."""

    @pytest.mark.asyncio
    async def test_handle_exec_successful_command(self) -> None:
        """Exec handler should execute command and return output."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "exec",
                "command": "echo",
                "args": ["hello"],
            },
        )

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(
                returncode=0,
                stdout="hello\n",
                stderr="",
            )

            result = await handle_exec(msg)

        assert result["type"] == "exec_result"
        assert result["exit_code"] == 0
        assert result["stdout"] == "hello\n"
        assert result["stderr"] == ""

    @pytest.mark.asyncio
    async def test_handle_exec_command_failure(self) -> None:
        """Exec handler should handle command failures."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "exec",
                "command": "false",
                "args": [],
            },
        )

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(
                returncode=1,
                stdout="",
                stderr="command failed",
            )

            result = await handle_exec(msg)

        assert result["type"] == "exec_result"
        assert result["exit_code"] == 1
        assert result["stderr"] == "command failed"

    @pytest.mark.asyncio
    async def test_handle_exec_timeout(self) -> None:
        """Exec handler should handle command timeouts."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "exec",
                "command": "sleep",
                "args": ["10"],
            },
        )

        with patch("subprocess.run") as mock_run:
            import subprocess
            mock_run.side_effect = subprocess.TimeoutExpired("sleep", 5)

            result = await handle_exec(msg)

        assert result["type"] == "exec_result"
        assert result["exit_code"] == -1
        assert "timeout" in result["stderr"].lower() or "timed out" in result["stderr"].lower()


class TestRebootHandler:
    """Test reboot command handler."""

    @pytest.mark.asyncio
    async def test_handle_reboot_immediate(self) -> None:
        """Reboot handler should schedule immediate reboot."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "reboot",
                "delay": 0,
            },
        )

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)

            result = await handle_reboot(msg)

        assert result["type"] == "reboot_result"
        assert result["success"] is True
        assert "reboot" in result["message"].lower()

        # Should have called shutdown command
        mock_run.assert_called_once()
        call_args = mock_run.call_args[0][0]
        assert "shutdown" in call_args or "reboot" in call_args

    @pytest.mark.asyncio
    async def test_handle_reboot_with_delay(self) -> None:
        """Reboot handler should schedule delayed reboot."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "reboot",
                "delay": 60,
            },
        )

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=0)
            with patch("time.time", return_value=1000.0):
                result = await handle_reboot(msg)

        assert result["type"] == "reboot_result"
        assert result["success"] is True
        assert result["scheduled_time"] is not None
        # Scheduled time should be current time + delay
        assert result["scheduled_time"] == 1060.0

    @pytest.mark.asyncio
    async def test_handle_reboot_failure(self) -> None:
        """Reboot handler should handle failures gracefully."""
        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "reboot",
                "delay": 0,
            },
        )

        with patch("subprocess.run") as mock_run:
            mock_run.return_value = Mock(returncode=1, stderr="Permission denied")

            result = await handle_reboot(msg)

        assert result["type"] == "reboot_result"
        assert result["success"] is False
        assert "permission" in result["message"].lower() or "failed" in result["message"].lower()


class TestUpdateConfigHandler:
    """Test update config command handler."""

    @pytest.mark.asyncio
    async def test_handle_update_config_success(self, tmp_path) -> None:
        """Update config handler should update configuration."""
        config_file = tmp_path / "config.yaml"
        config_file.write_text("log_level: INFO\nmax_retries: 3\n")

        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "update_config",
                "config": {
                    "log_level": "DEBUG",
                    "max_retries": 5,
                },
            },
        )

        with patch("styrene_bond_rpc.handlers.get_config_path", return_value=str(config_file)):
            result = await handle_update_config(msg)

        assert result["type"] == "update_config_result"
        assert result["success"] is True
        assert "log_level" in result["updated_keys"]
        assert "max_retries" in result["updated_keys"]

    @pytest.mark.asyncio
    async def test_handle_update_config_creates_new_file(self, tmp_path) -> None:
        """Update config handler should create config if it doesn't exist."""
        config_file = tmp_path / "new_config.yaml"

        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "update_config",
                "config": {
                    "setting": "value",
                },
            },
        )

        with patch("styrene_bond_rpc.handlers.get_config_path", return_value=str(config_file)):
            result = await handle_update_config(msg)

        assert result["type"] == "update_config_result"
        assert result["success"] is True
        assert config_file.exists()

    @pytest.mark.asyncio
    async def test_handle_update_config_invalid_yaml(self, tmp_path) -> None:
        """Update config handler should handle invalid YAML gracefully."""
        config_file = tmp_path / "invalid.yaml"
        config_file.write_text("invalid: yaml: :\n")

        msg = LXMFMessage(
            source_hash="client_hash",
            destination_hash="server_hash",
            timestamp=123.0,
            fields={
                "protocol": "rpc",
                "type": "update_config",
                "config": {
                    "setting": "value",
                },
            },
        )

        with patch("styrene_bond_rpc.handlers.get_config_path", return_value=str(config_file)):
            result = await handle_update_config(msg)

        # Should handle error gracefully and create new config
        assert result["type"] == "update_config_result"
        # Might succeed by creating new config, or fail with error
        assert "updated_keys" in result or "success" in result
