"""Tests for the Device class.

This module contains comprehensive tests for the Device endpoint handler,
including device operations, lock commands, accessory settings,
validation logic, and result types.
"""

from __future__ import annotations

import json
from typing import Any
from unittest.mock import AsyncMock

import pytest

from aiokwikset.device import (
    AccessoryAction,
    CommandResult,
    Device,
    DeviceStatus,
    LockAction,
    ValidationResult,
)
from aiokwikset.errors import (
    InvalidActionError,
    InvalidDeviceError,
    InvalidStatusError,
    InvalidUserError,
)

from .conftest import (
    MOCK_COMMAND_RESPONSE,
    MOCK_DEVICE_HISTORY,
    MOCK_DEVICE_INFO,
    MOCK_DEVICES,
    MOCK_VALID_DEVICE,
    MOCK_VALID_USER,
)

# ============================================================================
# Enum Tests
# ============================================================================


class TestLockAction:
    """Tests for LockAction enum."""

    def test_lock_action_values(self) -> None:
        """Test LockAction enum has correct values."""
        assert LockAction.LOCK.value == "lock"
        assert LockAction.UNLOCK.value == "unlock"

    def test_lock_action_is_string_enum(self) -> None:
        """Test LockAction is a string enum."""
        assert isinstance(LockAction.LOCK, str)
        assert LockAction.LOCK == "lock"


class TestDeviceStatus:
    """Tests for DeviceStatus enum."""

    def test_device_status_values(self) -> None:
        """Test DeviceStatus enum has correct values."""
        assert DeviceStatus.ENABLED.value == "true"
        assert DeviceStatus.DISABLED.value == "false"

    def test_device_status_is_string_enum(self) -> None:
        """Test DeviceStatus is a string enum."""
        assert isinstance(DeviceStatus.ENABLED, str)
        assert DeviceStatus.ENABLED == "true"


class TestAccessoryAction:
    """Tests for AccessoryAction enum."""

    def test_accessory_action_values(self) -> None:
        """Test AccessoryAction enum has correct values."""
        assert AccessoryAction.LED_STATUS.value == "ledstatus"
        assert AccessoryAction.AUDIO_STATUS.value == "audiostatus"
        assert AccessoryAction.SECURE_SCREEN_STATUS.value == "securescreenstatus"


# ============================================================================
# ValidationResult Tests
# ============================================================================


class TestValidationResult:
    """Tests for ValidationResult dataclass."""

    def test_default_state_is_valid(self) -> None:
        """Test ValidationResult defaults to valid state."""
        result = ValidationResult()

        assert result.is_valid is True
        assert result.errors == []
        assert result.warnings == []

    def test_add_error_marks_invalid(self) -> None:
        """Test add_error marks result as invalid."""
        result = ValidationResult()

        result.add_error("Test error")

        assert result.is_valid is False
        assert "Test error" in result.errors

    def test_add_warning_does_not_invalidate(self) -> None:
        """Test add_warning does not mark result as invalid."""
        result = ValidationResult()

        result.add_warning("Test warning")

        assert result.is_valid is True
        assert "Test warning" in result.warnings

    def test_add_multiple_errors(self) -> None:
        """Test adding multiple errors."""
        result = ValidationResult()

        result.add_error("Error 1")
        result.add_error("Error 2")

        assert result.is_valid is False
        assert len(result.errors) == 2

    def test_merge_combines_results(self) -> None:
        """Test merge combines two ValidationResults."""
        result1 = ValidationResult()
        result1.add_error("Error 1")
        result1.add_warning("Warning 1")

        result2 = ValidationResult()
        result2.add_warning("Warning 2")

        result1.merge(result2)

        assert result1.is_valid is False
        assert len(result1.errors) == 1
        assert len(result1.warnings) == 2

    def test_merge_invalid_result(self) -> None:
        """Test merging an invalid result marks combined as invalid."""
        result1 = ValidationResult()  # Valid

        result2 = ValidationResult()
        result2.add_error("Error from result2")

        result1.merge(result2)

        assert result1.is_valid is False


# ============================================================================
# CommandResult Tests
# ============================================================================


class TestCommandResult:
    """Tests for CommandResult dataclass."""

    def test_default_state_is_success(self) -> None:
        """Test CommandResult defaults to success state."""
        result = CommandResult()

        assert result.success is True
        assert result.data is None
        assert result.device_id is None
        assert result.action is None
        assert result.validation is None
        assert result.error_message is None

    def test_with_all_fields(self) -> None:
        """Test CommandResult with all fields populated."""
        validation = ValidationResult()
        result = CommandResult(
            success=True,
            data={"status": "ok"},
            device_id="SN12345",
            action="lock",
            validation=validation,
            error_message=None,
        )

        assert result.success is True
        assert result.data == {"status": "ok"}
        assert result.device_id == "SN12345"
        assert result.action == "lock"
        assert result.validation is validation

    def test_failed_result(self) -> None:
        """Test CommandResult for failed command."""
        result = CommandResult(
            success=False,
            device_id="SN12345",
            action="lock",
            error_message="Device not responding",
        )

        assert result.success is False
        assert result.error_message == "Device not responding"


# ============================================================================
# Device Initialization Tests
# ============================================================================


class TestDeviceInitialization:
    """Tests for Device class initialization."""

    def test_init_with_request_callable(self) -> None:
        """Test Device initialization with request callable."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        assert device._request is mock_request

    def test_uses_slots(self) -> None:
        """Test Device uses __slots__ for memory efficiency."""
        assert hasattr(Device, "__slots__")
        assert "_request" in Device.__slots__


# ============================================================================
# Validation Helper Tests
# ============================================================================


class TestDeviceValidation:
    """Tests for Device._validate_device method."""

    def test_validate_device_valid(self) -> None:
        """Test validation of valid device."""
        result = Device._validate_device(MOCK_VALID_DEVICE)

        assert result.is_valid is True
        assert len(result.errors) == 0

    def test_validate_device_none(self) -> None:
        """Test validation of None device."""
        result = Device._validate_device(None)

        assert result.is_valid is False
        assert any("cannot be None" in e for e in result.errors)

    def test_validate_device_not_dict(self) -> None:
        """Test validation of non-dict device."""
        result = Device._validate_device("not a dict")  # type: ignore

        assert result.is_valid is False
        assert any("must be a dictionary" in e for e in result.errors)

    def test_validate_device_missing_serialnumber(self) -> None:
        """Test validation of device missing serialnumber."""
        result = Device._validate_device({"devicename": "Test"})

        assert result.is_valid is False
        assert any("serialnumber" in e for e in result.errors)

    def test_validate_device_empty_serialnumber(self) -> None:
        """Test validation of device with empty serialnumber."""
        result = Device._validate_device({"serialnumber": ""})

        assert result.is_valid is False
        assert any("serialnumber" in e for e in result.errors)


class TestUserValidation:
    """Tests for Device._validate_user method."""

    def test_validate_user_valid(self) -> None:
        """Test validation of valid user."""
        result = Device._validate_user(MOCK_VALID_USER)

        assert result.is_valid is True
        assert len(result.errors) == 0

    def test_validate_user_none(self) -> None:
        """Test validation of None user."""
        result = Device._validate_user(None)

        assert result.is_valid is False
        assert any("cannot be None" in e for e in result.errors)

    def test_validate_user_not_dict(self) -> None:
        """Test validation of non-dict user."""
        result = Device._validate_user(123)  # type: ignore

        assert result.is_valid is False
        assert any("must be a dictionary" in e for e in result.errors)

    def test_validate_user_missing_names_warning(self) -> None:
        """Test validation warning for user without names."""
        result = Device._validate_user({})

        # Should be valid but with warning
        assert result.is_valid is True
        assert len(result.warnings) > 0
        assert any("no firstname or lastname" in w for w in result.warnings)

    def test_validate_user_firstname_only(self) -> None:
        """Test validation of user with firstname only."""
        result = Device._validate_user({"firstname": "John"})

        assert result.is_valid is True
        assert len(result.warnings) == 0

    def test_validate_user_lastname_only(self) -> None:
        """Test validation of user with lastname only."""
        result = Device._validate_user({"lastname": "Doe"})

        assert result.is_valid is True
        assert len(result.warnings) == 0


class TestLockActionValidation:
    """Tests for Device._validate_lock_action method."""

    def test_validate_lock_action_lock(self) -> None:
        """Test validation of 'lock' action."""
        result = Device._validate_lock_action("lock")

        assert result.is_valid is True

    def test_validate_lock_action_unlock(self) -> None:
        """Test validation of 'unlock' action."""
        result = Device._validate_lock_action("unlock")

        assert result.is_valid is True

    def test_validate_lock_action_invalid(self) -> None:
        """Test validation of invalid lock action."""
        result = Device._validate_lock_action("toggle")

        assert result.is_valid is False
        assert any("Invalid lock action" in e for e in result.errors)
        assert any("toggle" in e for e in result.errors)


class TestStatusValidation:
    """Tests for Device._validate_status method."""

    def test_validate_status_true(self) -> None:
        """Test validation of 'true' status."""
        result = Device._validate_status("true")

        assert result.is_valid is True

    def test_validate_status_false(self) -> None:
        """Test validation of 'false' status."""
        result = Device._validate_status("false")

        assert result.is_valid is True

    def test_validate_status_invalid(self) -> None:
        """Test validation of invalid status."""
        result = Device._validate_status("maybe")

        assert result.is_valid is False
        assert any("Invalid status" in e for e in result.errors)


class TestAccessoryActionValidation:
    """Tests for Device._validate_accessory_action method."""

    def test_validate_accessory_action_led(self) -> None:
        """Test validation of LED status action."""
        result = Device._validate_accessory_action("ledstatus")

        assert result.is_valid is True

    def test_validate_accessory_action_audio(self) -> None:
        """Test validation of audio status action."""
        result = Device._validate_accessory_action("audiostatus")

        assert result.is_valid is True

    def test_validate_accessory_action_secure_screen(self) -> None:
        """Test validation of secure screen status action."""
        result = Device._validate_accessory_action("securescreenstatus")

        assert result.is_valid is True

    def test_validate_accessory_action_invalid(self) -> None:
        """Test validation of invalid accessory action."""
        result = Device._validate_accessory_action("brightness")

        assert result.is_valid is False
        assert any("Invalid accessory action" in e for e in result.errors)


# ============================================================================
# Get Operations Tests
# ============================================================================


class TestGetDevices:
    """Tests for Device.get_devices method."""

    @pytest.mark.asyncio
    async def test_get_devices_success(self) -> None:
        """Test successful get_devices call."""
        mock_request = AsyncMock(return_value=MOCK_DEVICES)
        device = Device(mock_request)

        result = await device.get_devices("home-001")

        assert len(result) == 2
        assert result[0]["devicename"] == "Front Door"
        mock_request.assert_called_once()

    @pytest.mark.asyncio
    async def test_get_devices_empty_home_id_raises(self) -> None:
        """Test get_devices raises ValueError for empty home_id."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_devices("")

        assert "home_id cannot be empty" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_get_devices_none_home_id_raises(self) -> None:
        """Test get_devices raises ValueError for None home_id."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_devices(None)  # type: ignore

        assert "home_id cannot be empty" in str(exc_info.value)


class TestGetDeviceInfo:
    """Tests for Device.get_device_info method."""

    @pytest.mark.asyncio
    async def test_get_device_info_success(self) -> None:
        """Test successful get_device_info call."""
        mock_request = AsyncMock(return_value=MOCK_DEVICE_INFO)
        device = Device(mock_request)

        result = await device.get_device_info("device-001")

        assert result is not None
        assert result["devicename"] == "Front Door"
        assert result["batterypercentage"] == 85

    @pytest.mark.asyncio
    async def test_get_device_info_empty_data(self) -> None:
        """Test get_device_info returns None for empty data."""
        mock_request = AsyncMock(return_value={"data": []})
        device = Device(mock_request)

        result = await device.get_device_info("nonexistent")

        assert result is None

    @pytest.mark.asyncio
    async def test_get_device_info_empty_device_id_raises(self) -> None:
        """Test get_device_info raises ValueError for empty device_id."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_device_info("")

        assert "device_id cannot be empty" in str(exc_info.value)


class TestGetDeviceHistory:
    """Tests for Device.get_device_history method."""

    @pytest.mark.asyncio
    async def test_get_device_history_success(self) -> None:
        """Test successful get_device_history call."""
        mock_request = AsyncMock(return_value=MOCK_DEVICE_HISTORY)
        device = Device(mock_request)

        result = await device.get_device_history("1041d5b8b6eb2ea917")

        assert result["total"] == 837
        assert len(result["data"]) == 3
        assert result["data"][0]["event"] == "Locked"
        assert result["data"][0]["user"] == "Joseph apikwik"
        assert result["data"][1]["event"] == "Unlocked"
        assert result["issues"] == []
        assert result["lastupdatestatus"] == 1770951294
        mock_request.assert_called_once()
        call_args = mock_request.call_args
        assert "history_v4" in call_args[0][1]
        assert "top=200" in call_args[0][1]

    @pytest.mark.asyncio
    async def test_get_device_history_custom_top(self) -> None:
        """Test get_device_history with custom top parameter."""
        mock_request = AsyncMock(return_value=MOCK_DEVICE_HISTORY)
        device = Device(mock_request)

        await device.get_device_history("1041d5b8b6eb2ea917", top=50)

        call_args = mock_request.call_args
        assert "top=50" in call_args[0][1]

    @pytest.mark.asyncio
    async def test_get_device_history_empty_device_id_raises(self) -> None:
        """Test get_device_history raises ValueError for empty device_id."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_device_history("")

        assert "device_id cannot be empty" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_get_device_history_none_device_id_raises(self) -> None:
        """Test get_device_history raises ValueError for None device_id."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_device_history(None)  # type: ignore

        assert "device_id cannot be empty" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_get_device_history_invalid_top_raises(self) -> None:
        """Test get_device_history raises ValueError for non-positive top."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_device_history("device-001", top=0)

        assert "top must be a positive integer" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_get_device_history_negative_top_raises(self) -> None:
        """Test get_device_history raises ValueError for negative top."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(ValueError) as exc_info:
            await device.get_device_history("device-001", top=-1)

        assert "top must be a positive integer" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_get_device_history_empty_data(self) -> None:
        """Test get_device_history with empty event list."""
        empty_history: dict[str, Any] = {
            "data": [],
            "issues": [],
            "total": 0,
            "lastupdatestatus": 0,
        }
        mock_request = AsyncMock(return_value=empty_history)
        device = Device(mock_request)

        result = await device.get_device_history("device-001")

        assert result["data"] == []
        assert result["total"] == 0


# ============================================================================
# Lock Action Tests
# ============================================================================


class TestLockDevice:
    """Tests for Device.lock_device method."""

    @pytest.mark.asyncio
    async def test_lock_device_success(self) -> None:
        """Test successful lock_device call."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.lock_device(
            MOCK_VALID_DEVICE.copy(),
            MOCK_VALID_USER.copy(),
        )

        assert result.success is True
        assert result.action == "lock"
        assert result.device_id == "SN12345"

    @pytest.mark.asyncio
    async def test_lock_device_invalid_device(self) -> None:
        """Test lock_device raises InvalidDeviceError for invalid device."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidDeviceError) as exc_info:
            await device.lock_device(None, MOCK_VALID_USER.copy())  # type: ignore

        assert "Device cannot be None" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_lock_device_invalid_user(self) -> None:
        """Test lock_device raises InvalidUserError for invalid user."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidUserError):
            await device.lock_device(MOCK_VALID_DEVICE.copy(), None)  # type: ignore


class TestUnlockDevice:
    """Tests for Device.unlock_device method."""

    @pytest.mark.asyncio
    async def test_unlock_device_success(self) -> None:
        """Test successful unlock_device call."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.unlock_device(
            MOCK_VALID_DEVICE.copy(),
            MOCK_VALID_USER.copy(),
        )

        assert result.success is True
        assert result.action == "unlock"


class TestLockActionInternal:
    """Tests for Device._lock_action internal method."""

    @pytest.mark.asyncio
    async def test_lock_action_invalid_action(self) -> None:
        """Test _lock_action raises InvalidActionError for invalid action."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidActionError) as exc_info:
            await device._lock_action(
                MOCK_VALID_DEVICE.copy(),
                MOCK_VALID_USER.copy(),
                "toggle",
            )

        assert exc_info.value.action == "toggle"
        assert "lock" in exc_info.value.valid_actions
        assert "unlock" in exc_info.value.valid_actions

    @pytest.mark.asyncio
    async def test_lock_action_truncates_name(self) -> None:
        """Test _lock_action truncates user name to MAX_NAME_LENGTH."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        long_user = {
            "firstname": "Alexander",
            "lastname": "Hamilton",
        }

        await device._lock_action(
            MOCK_VALID_DEVICE.copy(),
            long_user,
            "lock",
        )

        # Verify the request payload
        call_args = mock_request.call_args
        data = json.loads(call_args.kwargs.get("data", "{}"))
        source = json.loads(data.get("source", "{}"))

        # Name should be truncated to MAX_NAME_LENGTH (7)
        assert len(source["name"]) <= Device.MAX_NAME_LENGTH

    @pytest.mark.asyncio
    async def test_lock_action_handles_request_error(self) -> None:
        """Test _lock_action returns failed CommandResult on error."""
        mock_request = AsyncMock(side_effect=Exception("Network error"))
        device = Device(mock_request)

        result = await device._lock_action(
            MOCK_VALID_DEVICE.copy(),
            MOCK_VALID_USER.copy(),
            "lock",
        )

        assert result.success is False
        assert "Network error" in result.error_message

    @pytest.mark.asyncio
    async def test_lock_action_missing_device_fields(self) -> None:
        """Test _lock_action raises with missing device fields info."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidDeviceError) as exc_info:
            await device._lock_action(
                {"devicename": "Test"},  # Missing serialnumber
                MOCK_VALID_USER.copy(),
                "lock",
            )

        assert "serialnumber" in exc_info.value.missing_fields


# ============================================================================
# Accessory Action Tests
# ============================================================================


class TestSetLedStatus:
    """Tests for Device.set_ledstatus method."""

    @pytest.mark.asyncio
    async def test_set_ledstatus_enable(self) -> None:
        """Test enabling LED status."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_ledstatus(MOCK_VALID_DEVICE.copy(), "true")

        assert result.success is True
        assert "ledstatus" in result.action

    @pytest.mark.asyncio
    async def test_set_ledstatus_disable(self) -> None:
        """Test disabling LED status."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_ledstatus(MOCK_VALID_DEVICE.copy(), "false")

        assert result.success is True

    @pytest.mark.asyncio
    async def test_set_ledstatus_invalid_status(self) -> None:
        """Test set_ledstatus raises InvalidStatusError for invalid status."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidStatusError) as exc_info:
            await device.set_ledstatus(MOCK_VALID_DEVICE.copy(), "on")

        assert exc_info.value.status == "on"
        assert "true" in exc_info.value.valid_statuses
        assert "false" in exc_info.value.valid_statuses


class TestSetAudioStatus:
    """Tests for Device.set_audiostatus method."""

    @pytest.mark.asyncio
    async def test_set_audiostatus_enable(self) -> None:
        """Test enabling audio status."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_audiostatus(MOCK_VALID_DEVICE.copy(), "true")

        assert result.success is True


class TestSetSecureScreenStatus:
    """Tests for Device.set_securescreenstatus method."""

    @pytest.mark.asyncio
    async def test_set_securescreenstatus_enable(self) -> None:
        """Test enabling secure screen status."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_securescreenstatus(MOCK_VALID_DEVICE.copy(), "true")

        assert result.success is True


class TestSetAutolockState:
    """Tests for Device.set_autolock_enabled method.

    Auto-lock uses a dedicated endpoint and payload shape distinct from the
    generic accessory-status setter, so it is exercised on the boolean
    convenience method directly.
    """

    @pytest.mark.asyncio
    async def test_set_autolock_enable_uses_dedicated_endpoint(self) -> None:
        """Test set_autolock_enabled posts to /autolock with camelCase boolean."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_autolock_enabled(MOCK_VALID_DEVICE.copy(), True)

        assert result.success is True
        assert result.action == "autolock"
        method, url = mock_request.call_args.args[:2]
        assert method == "patch"
        assert url.endswith("/autolock")
        data = json.loads(mock_request.call_args.kwargs.get("data", "{}"))
        assert data == {"autoLockEnabled": True}

    @pytest.mark.asyncio
    async def test_set_autolock_disable(self) -> None:
        """Test set_autolock_enabled with False sends boolean false."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_autolock_enabled(MOCK_VALID_DEVICE.copy(), False)

        assert result.success is True
        data = json.loads(mock_request.call_args.kwargs.get("data", "{}"))
        assert data == {"autoLockEnabled": False}

    @pytest.mark.asyncio
    async def test_set_autolock_invalid_device(self) -> None:
        """Test set_autolock_enabled raises InvalidDeviceError for invalid device."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidDeviceError):
            await device.set_autolock_enabled(None, True)  # type: ignore[arg-type]

    @pytest.mark.asyncio
    async def test_set_autolock_handles_request_error(self) -> None:
        """Test set_autolock_enabled returns failed CommandResult on error."""
        mock_request = AsyncMock(side_effect=Exception("Server error"))
        device = Device(mock_request)

        result = await device.set_autolock_enabled(MOCK_VALID_DEVICE.copy(), True)

        assert result.success is False
        assert "Server error" in result.error_message


class TestSetActionInternal:
    """Tests for Device._set_action internal method."""

    @pytest.mark.asyncio
    async def test_set_action_invalid_device(self) -> None:
        """Test _set_action raises InvalidDeviceError for invalid device."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidDeviceError):
            await device._set_action(None, "ledstatus", "true")  # type: ignore

    @pytest.mark.asyncio
    async def test_set_action_invalid_action(self) -> None:
        """Test _set_action raises InvalidActionError for invalid action."""
        mock_request = AsyncMock()
        device = Device(mock_request)

        with pytest.raises(InvalidActionError):
            await device._set_action(MOCK_VALID_DEVICE.copy(), "invalid", "true")

    @pytest.mark.asyncio
    async def test_set_action_handles_request_error(self) -> None:
        """Test _set_action returns failed CommandResult on error."""
        mock_request = AsyncMock(side_effect=Exception("Server error"))
        device = Device(mock_request)

        result = await device._set_action(
            MOCK_VALID_DEVICE.copy(),
            "ledstatus",
            "true",
        )

        assert result.success is False
        assert "Server error" in result.error_message


# ============================================================================
# Convenience Method Tests
# ============================================================================


class TestConvenienceMethods:
    """Tests for convenience methods with boolean parameters."""

    @pytest.mark.asyncio
    async def test_set_led_enabled_true(self) -> None:
        """Test set_led_enabled with True."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_led_enabled(MOCK_VALID_DEVICE.copy(), True)

        assert result.success is True
        # Verify correct status was sent
        call_args = mock_request.call_args
        data = json.loads(call_args.kwargs.get("data", "{}"))
        assert data.get("ledstatus") == "true"

    @pytest.mark.asyncio
    async def test_set_led_enabled_false(self) -> None:
        """Test set_led_enabled with False."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_led_enabled(MOCK_VALID_DEVICE.copy(), False)

        assert result.success is True
        call_args = mock_request.call_args
        data = json.loads(call_args.kwargs.get("data", "{}"))
        assert data.get("ledstatus") == "false"

    @pytest.mark.asyncio
    async def test_set_audio_enabled_true(self) -> None:
        """Test set_audio_enabled with True."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_audio_enabled(MOCK_VALID_DEVICE.copy(), True)

        assert result.success is True

    @pytest.mark.asyncio
    async def test_set_audio_enabled_false(self) -> None:
        """Test set_audio_enabled with False."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_audio_enabled(MOCK_VALID_DEVICE.copy(), False)

        assert result.success is True

    @pytest.mark.asyncio
    async def test_set_secure_screen_enabled_true(self) -> None:
        """Test set_secure_screen_enabled with True."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_secure_screen_enabled(MOCK_VALID_DEVICE.copy(), True)

        assert result.success is True

    @pytest.mark.asyncio
    async def test_set_secure_screen_enabled_false(self) -> None:
        """Test set_secure_screen_enabled with False."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_secure_screen_enabled(MOCK_VALID_DEVICE.copy(), False)

        assert result.success is True

    @pytest.mark.asyncio
    async def test_set_autolock_enabled_true(self) -> None:
        """Test set_autolock_enabled with True."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_autolock_enabled(MOCK_VALID_DEVICE.copy(), True)

        assert result.success is True
        call_args = mock_request.call_args
        data = json.loads(call_args.kwargs.get("data", "{}"))
        assert data.get("autoLockEnabled") is True

    @pytest.mark.asyncio
    async def test_set_autolock_enabled_false(self) -> None:
        """Test set_autolock_enabled with False."""
        mock_request = AsyncMock(return_value=MOCK_COMMAND_RESPONSE)
        device = Device(mock_request)

        result = await device.set_autolock_enabled(MOCK_VALID_DEVICE.copy(), False)

        assert result.success is True
        call_args = mock_request.call_args
        data = json.loads(call_args.kwargs.get("data", "{}"))
        assert data.get("autoLockEnabled") is False
