"""Unit tests for the per-provider DeviceConfig hierarchy and Device.to_config()."""

import pytest

from ledgrab.core.devices.device_config import (
    AdalightConfig,
    AmbiLEDConfig,
    BaseDeviceConfig,
    BLEConfig,
    ChromaConfig,
    DemoConfig,
    DMXConfig,
    ESPNowConfig,
    GameSenseConfig,
    GroupConfig,
    HueConfig,
    MockConfig,
    MQTTConfig,
    OpenRGBConfig,
    SPIConfig,
    USBHIDConfig,
    WLEDConfig,
    WSConfig,
)
from ledgrab.storage.device_store import Device


def _make_device(**kwargs) -> Device:
    defaults = dict(
        device_id="dev_test01",
        name="Test Device",
        url="http://192.168.1.100",
        led_count=60,
        software_brightness=200,
        auto_shutdown=True,
        rgbw=True,
        baud_rate=115200,
        send_latency_ms=5,
        zone_mode="separate",
        dmx_protocol="sacn",
        dmx_start_universe=1,
        dmx_start_channel=2,
        espnow_peer_mac="AA:BB:CC:DD:EE:FF",
        espnow_channel=6,
        hue_username="hue_user",
        hue_client_key="hue_key",
        hue_entertainment_group_id="grp_1",
        spi_speed_hz=1600000,
        spi_led_type="APA102",
        chroma_device_type="keyboard",
        gamesense_device_type="mouse",
        ble_family="govee",
        ble_govee_key="secret_key",
        group_device_ids=["dev_a", "dev_b"],
        group_mode="independent",
    )
    defaults.update(kwargs)
    return Device(**defaults)


class TestBaseDeviceConfigDefaults:
    def test_defaults(self):
        cfg = BaseDeviceConfig(device_id="d1", device_url="http://x", led_count=10)
        assert cfg.software_brightness == 255
        assert cfg.test_mode_active is False
        assert cfg.auto_shutdown is False
        assert cfg.rgbw is False

    def test_frozen(self):
        cfg = BaseDeviceConfig(device_id="d1", device_url="http://x", led_count=10)
        with pytest.raises(Exception):
            cfg.led_count = 20  # type: ignore[misc]


class TestWLEDConfig:
    def test_to_config_returns_wled(self):
        device = _make_device(device_type="wled")
        cfg = device.to_config()
        assert isinstance(cfg, WLEDConfig)
        assert cfg.device_type == "wled"

    def test_base_fields_mapped(self):
        device = _make_device(device_type="wled")
        cfg = device.to_config()
        assert cfg.device_id == "dev_test01"
        assert cfg.device_url == "http://192.168.1.100"
        assert cfg.led_count == 60
        assert cfg.software_brightness == 200
        assert cfg.auto_shutdown is True
        assert cfg.rgbw is True

    def test_use_ddp_defaults_false(self):
        cfg = _make_device(device_type="wled").to_config()
        assert cfg.use_ddp is False  # type: ignore[union-attr]

    def test_test_mode_active_defaults_false(self):
        # test_mode_active is runtime state, not stored on Device
        cfg = _make_device(device_type="wled").to_config()
        assert cfg.test_mode_active is False

    def test_wled_has_no_baud_rate(self):
        cfg = _make_device(device_type="wled").to_config()
        assert not hasattr(cfg, "baud_rate")

    def test_wled_has_no_ble_fields(self):
        cfg = _make_device(device_type="wled").to_config()
        assert not hasattr(cfg, "ble_family")
        assert not hasattr(cfg, "ble_govee_key")


class TestAdalightConfig:
    def test_to_config_returns_adalight(self):
        cfg = _make_device(device_type="adalight").to_config()
        assert isinstance(cfg, AdalightConfig)
        assert cfg.device_type == "adalight"

    def test_baud_rate_mapped(self):
        cfg = _make_device(device_type="adalight").to_config()
        assert cfg.baud_rate == 115200  # type: ignore[union-attr]

    def test_adalight_has_no_dmx_fields(self):
        cfg = _make_device(device_type="adalight").to_config()
        assert not hasattr(cfg, "dmx_protocol")


class TestAmbiLEDConfig:
    def test_to_config_returns_ambiled(self):
        cfg = _make_device(device_type="ambiled").to_config()
        assert isinstance(cfg, AmbiLEDConfig)
        assert cfg.device_type == "ambiled"

    def test_baud_rate_mapped(self):
        cfg = _make_device(device_type="ambiled").to_config()
        assert cfg.baud_rate == 115200  # type: ignore[union-attr]


class TestDMXConfig:
    def test_to_config_returns_dmx(self):
        cfg = _make_device(device_type="dmx").to_config()
        assert isinstance(cfg, DMXConfig)
        assert cfg.device_type == "dmx"

    def test_dmx_fields_mapped(self):
        cfg = _make_device(device_type="dmx").to_config()
        assert cfg.dmx_protocol == "sacn"  # type: ignore[union-attr]
        assert cfg.dmx_start_universe == 1  # type: ignore[union-attr]
        assert cfg.dmx_start_channel == 2  # type: ignore[union-attr]

    def test_dmx_has_no_baud_rate(self):
        cfg = _make_device(device_type="dmx").to_config()
        assert not hasattr(cfg, "baud_rate")


class TestESPNowConfig:
    def test_to_config_returns_espnow(self):
        cfg = _make_device(device_type="espnow").to_config()
        assert isinstance(cfg, ESPNowConfig)
        assert cfg.device_type == "espnow"

    def test_espnow_fields_mapped(self):
        cfg = _make_device(device_type="espnow").to_config()
        assert cfg.baud_rate == 115200  # type: ignore[union-attr]
        assert cfg.espnow_peer_mac == "AA:BB:CC:DD:EE:FF"  # type: ignore[union-attr]
        assert cfg.espnow_channel == 6  # type: ignore[union-attr]


class TestHueConfig:
    def test_to_config_returns_hue(self):
        cfg = _make_device(device_type="hue").to_config()
        assert isinstance(cfg, HueConfig)
        assert cfg.device_type == "hue"

    def test_hue_fields_mapped(self):
        cfg = _make_device(device_type="hue").to_config()
        assert cfg.hue_username == "hue_user"  # type: ignore[union-attr]
        assert cfg.hue_client_key == "hue_key"  # type: ignore[union-attr]
        assert cfg.hue_entertainment_group_id == "grp_1"  # type: ignore[union-attr]


class TestSPIConfig:
    def test_to_config_returns_spi(self):
        cfg = _make_device(device_type="spi").to_config()
        assert isinstance(cfg, SPIConfig)
        assert cfg.device_type == "spi"

    def test_spi_fields_mapped(self):
        cfg = _make_device(device_type="spi").to_config()
        assert cfg.spi_speed_hz == 1600000  # type: ignore[union-attr]
        assert cfg.spi_led_type == "APA102"  # type: ignore[union-attr]


class TestChromaConfig:
    def test_to_config_returns_chroma(self):
        cfg = _make_device(device_type="chroma").to_config()
        assert isinstance(cfg, ChromaConfig)
        assert cfg.device_type == "chroma"

    def test_chroma_device_type_mapped(self):
        cfg = _make_device(device_type="chroma").to_config()
        assert cfg.chroma_device_type == "keyboard"  # type: ignore[union-attr]


class TestGameSenseConfig:
    def test_to_config_returns_gamesense(self):
        cfg = _make_device(device_type="gamesense").to_config()
        assert isinstance(cfg, GameSenseConfig)
        assert cfg.device_type == "gamesense"

    def test_gamesense_device_type_mapped(self):
        cfg = _make_device(device_type="gamesense").to_config()
        assert cfg.gamesense_device_type == "mouse"  # type: ignore[union-attr]


class TestBLEConfig:
    def test_to_config_returns_ble(self):
        cfg = _make_device(device_type="ble").to_config()
        assert isinstance(cfg, BLEConfig)
        assert cfg.device_type == "ble"

    def test_ble_fields_mapped(self):
        cfg = _make_device(device_type="ble").to_config()
        assert cfg.ble_family == "govee"  # type: ignore[union-attr]
        assert cfg.ble_govee_key == "secret_key"  # type: ignore[union-attr]

    def test_ble_has_no_dmx_fields(self):
        cfg = _make_device(device_type="ble").to_config()
        assert not hasattr(cfg, "dmx_protocol")


class TestGroupConfig:
    def test_to_config_returns_group(self):
        cfg = _make_device(device_type="group").to_config()
        assert isinstance(cfg, GroupConfig)
        assert cfg.device_type == "group"

    def test_group_fields_mapped(self):
        cfg = _make_device(device_type="group").to_config()
        assert cfg.group_mode == "independent"  # type: ignore[union-attr]
        assert cfg.group_device_ids == ["dev_a", "dev_b"]  # type: ignore[union-attr]

    def test_group_device_ids_is_copy(self):
        device = _make_device(device_type="group")
        cfg = device.to_config()
        # Modifying the device's list should not affect the config snapshot
        device.group_device_ids.append("dev_c")
        assert "dev_c" not in cfg.group_device_ids  # type: ignore[union-attr]

    def test_group_has_no_ble_fields(self):
        cfg = _make_device(device_type="group").to_config()
        assert not hasattr(cfg, "ble_family")


class TestOpenRGBConfig:
    def test_to_config_returns_openrgb(self):
        cfg = _make_device(device_type="openrgb").to_config()
        assert isinstance(cfg, OpenRGBConfig)
        assert cfg.device_type == "openrgb"

    def test_zone_mode_mapped(self):
        cfg = _make_device(device_type="openrgb").to_config()
        assert cfg.zone_mode == "separate"  # type: ignore[union-attr]


class TestMockConfig:
    def test_to_config_returns_mock(self):
        cfg = _make_device(device_type="mock").to_config()
        assert isinstance(cfg, MockConfig)
        assert cfg.device_type == "mock"

    def test_send_latency_ms_mapped(self):
        cfg = _make_device(device_type="mock").to_config()
        assert cfg.send_latency_ms == 5  # type: ignore[union-attr]


class TestDemoConfig:
    def test_to_config_returns_demo(self):
        cfg = _make_device(device_type="demo").to_config()
        assert isinstance(cfg, DemoConfig)
        assert cfg.device_type == "demo"

    def test_send_latency_ms_mapped(self):
        cfg = _make_device(device_type="demo").to_config()
        assert cfg.send_latency_ms == 5  # type: ignore[union-attr]


class TestMQTTConfig:
    def test_to_config_returns_mqtt(self):
        cfg = _make_device(device_type="mqtt").to_config()
        assert isinstance(cfg, MQTTConfig)
        assert cfg.device_type == "mqtt"


class TestWSConfig:
    def test_to_config_returns_ws(self):
        cfg = _make_device(device_type="ws").to_config()
        assert isinstance(cfg, WSConfig)
        assert cfg.device_type == "ws"


class TestUSBHIDConfig:
    def test_to_config_returns_usbhid(self):
        cfg = _make_device(device_type="usbhid").to_config()
        assert isinstance(cfg, USBHIDConfig)
        assert cfg.device_type == "usbhid"


class TestUnknownDeviceType:
    def test_raises_on_unknown_type(self):
        device = _make_device(device_type="unknown_future_type")
        with pytest.raises(ValueError, match="Unknown device type"):
            device.to_config()


class TestFieldIsolation:
    """Irrelevant Device fields must not leak into the wrong config type."""

    def test_wled_has_no_espnow_fields(self):
        cfg = _make_device(device_type="wled").to_config()
        assert not hasattr(cfg, "espnow_peer_mac")
        assert not hasattr(cfg, "espnow_channel")

    def test_hue_has_no_spi_fields(self):
        cfg = _make_device(device_type="hue").to_config()
        assert not hasattr(cfg, "spi_speed_hz")
        assert not hasattr(cfg, "spi_led_type")

    def test_adalight_has_no_group_fields(self):
        cfg = _make_device(device_type="adalight").to_config()
        assert not hasattr(cfg, "group_mode")
        assert not hasattr(cfg, "group_device_ids")

    def test_mqtt_has_no_hue_fields(self):
        cfg = _make_device(device_type="mqtt").to_config()
        assert not hasattr(cfg, "hue_username")
        assert not hasattr(cfg, "hue_client_key")
