"""Test the Kuler Sky lights."""

from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch

from bleak.backends.device import BLEDevice
import pykulersky
import pytest

from homeassistant.components.kulersky.const import DOMAIN
from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_MODE,
    ATTR_HS_COLOR,
    ATTR_RGB_COLOR,
    ATTR_RGBW_COLOR,
    ATTR_SUPPORTED_COLOR_MODES,
    ATTR_XY_COLOR,
    SCAN_INTERVAL,
    ColorMode,
)
from homeassistant.const import (
    ATTR_ENTITY_ID,
    ATTR_FRIENDLY_NAME,
    ATTR_SUPPORTED_FEATURES,
    CONF_ADDRESS,
    STATE_OFF,
    STATE_ON,
    STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_component import async_update_entity
from homeassistant.util import dt as dt_util

from tests.common import MockConfigEntry, async_fire_time_changed


@pytest.fixture
def mock_ble_device() -> Generator[MagicMock]:
    """Mock BLEDevice."""
    with patch(
        "homeassistant.components.kulersky.async_ble_device_from_address",
        return_value=BLEDevice(address="AA:BB:CC:11:22:33", name="Bedroom", details={}),
    ) as ble_device:
        yield ble_device


@pytest.fixture
async def mock_entry() -> MockConfigEntry:
    """Create a mock light entity."""
    return MockConfigEntry(
        domain=DOMAIN,
        data={CONF_ADDRESS: "AA:BB:CC:11:22:33"},
        title="Bedroom",
        version=2,
    )


@pytest.fixture
async def mock_light(
    hass: HomeAssistant, mock_entry: MockConfigEntry, mock_ble_device: MagicMock
) -> Generator[AsyncMock]:
    """Mock pykulersky light."""
    light = AsyncMock()
    light.address = "AA:BB:CC:11:22:33"
    light.name = "Bedroom"
    light.connect.return_value = True
    light.get_color.return_value = (0, 0, 0, 0)

    with patch(
        "pykulersky.Light",
        return_value=light,
    ):
        mock_entry.add_to_hass(hass)
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()

        assert light.connect.called

        yield light


async def test_init(hass: HomeAssistant, mock_light: AsyncMock) -> None:
    """Test platform setup."""
    state = hass.states.get("light.bedroom")
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        ATTR_FRIENDLY_NAME: "Bedroom",
        ATTR_SUPPORTED_COLOR_MODES: [ColorMode.RGBW],
        ATTR_SUPPORTED_FEATURES: 0,
        ATTR_COLOR_MODE: None,
        ATTR_BRIGHTNESS: None,
        ATTR_HS_COLOR: None,
        ATTR_RGB_COLOR: None,
        ATTR_XY_COLOR: None,
        ATTR_RGBW_COLOR: None,
    }


async def test_remove_entry(
    hass: HomeAssistant, mock_light: MagicMock, mock_entry: MockConfigEntry
) -> None:
    """Test platform setup."""
    await hass.config_entries.async_remove(mock_entry.entry_id)

    assert mock_light.disconnect.called


async def test_remove_entry_exceptions_caught(
    hass: HomeAssistant, mock_light: MagicMock, mock_entry: MockConfigEntry
) -> None:
    """Assert that disconnect exceptions are caught."""
    mock_light.disconnect.side_effect = pykulersky.PykulerskyException("Mock error")
    await hass.config_entries.async_remove(mock_entry.entry_id)

    assert mock_light.disconnect.called


async def test_update_exception(hass: HomeAssistant, mock_light: MagicMock) -> None:
    """Test platform setup."""

    mock_light.get_color.side_effect = pykulersky.PykulerskyException
    await async_update_entity(hass, "light.bedroom")
    state = hass.states.get("light.bedroom")
    assert state is not None
    assert state.state == STATE_UNAVAILABLE


async def test_light_turn_on(hass: HomeAssistant, mock_light: MagicMock) -> None:
    """Test KulerSkyLight turn_on."""
    mock_light.get_color.return_value = (255, 255, 255, 255)
    await hass.services.async_call(
        "light",
        "turn_on",
        {ATTR_ENTITY_ID: "light.bedroom"},
        blocking=True,
    )
    await hass.async_block_till_done()
    mock_light.set_color.assert_called_with(255, 255, 255, 255)

    mock_light.get_color.return_value = (50, 50, 50, 50)
    await hass.services.async_call(
        "light",
        "turn_on",
        {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 50},
        blocking=True,
    )
    await hass.async_block_till_done()
    mock_light.set_color.assert_called_with(50, 50, 50, 50)

    mock_light.get_color.return_value = (50, 25, 13, 6)
    await hass.services.async_call(
        "light",
        "turn_on",
        {ATTR_ENTITY_ID: "light.bedroom", ATTR_RGBW_COLOR: (255, 128, 64, 32)},
        blocking=True,
    )
    await hass.async_block_till_done()
    mock_light.set_color.assert_called_with(50, 25, 13, 6)

    # RGB color is converted to RGBW by assigning the white component to the white
    # channel, see color_rgb_to_rgbw
    mock_light.get_color.return_value = (0, 17, 50, 17)
    await hass.services.async_call(
        "light",
        "turn_on",
        {ATTR_ENTITY_ID: "light.bedroom", ATTR_RGB_COLOR: (64, 128, 255)},
        blocking=True,
    )
    await hass.async_block_till_done()
    mock_light.set_color.assert_called_with(0, 17, 50, 17)

    # HS color is converted to RGBW by assigning the white component to the white
    # channel, see color_rgb_to_rgbw
    mock_light.get_color.return_value = (50, 41, 0, 50)
    await hass.services.async_call(
        "light",
        "turn_on",
        {ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (50, 50)},
        blocking=True,
    )
    await hass.async_block_till_done()
    mock_light.set_color.assert_called_with(50, 41, 0, 50)


async def test_light_turn_off(hass: HomeAssistant, mock_light: MagicMock) -> None:
    """Test KulerSkyLight turn_on."""
    mock_light.get_color.return_value = (0, 0, 0, 0)
    await hass.services.async_call(
        "light",
        "turn_off",
        {ATTR_ENTITY_ID: "light.bedroom"},
        blocking=True,
    )
    await hass.async_block_till_done()
    mock_light.set_color.assert_called_with(0, 0, 0, 0)


async def test_light_update(hass: HomeAssistant, mock_light: MagicMock) -> None:
    """Test KulerSkyLight update."""
    utcnow = dt_util.utcnow()

    state = hass.states.get("light.bedroom")
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        ATTR_FRIENDLY_NAME: "Bedroom",
        ATTR_SUPPORTED_COLOR_MODES: [ColorMode.RGBW],
        ATTR_SUPPORTED_FEATURES: 0,
        ATTR_COLOR_MODE: None,
        ATTR_BRIGHTNESS: None,
        ATTR_HS_COLOR: None,
        ATTR_RGB_COLOR: None,
        ATTR_RGBW_COLOR: None,
        ATTR_XY_COLOR: None,
    }

    # Test an exception during discovery
    mock_light.get_color.side_effect = pykulersky.PykulerskyException("TEST")
    utcnow = utcnow + SCAN_INTERVAL
    async_fire_time_changed(hass, utcnow)
    await hass.async_block_till_done()

    state = hass.states.get("light.bedroom")
    assert state.state == STATE_UNAVAILABLE
    assert dict(state.attributes) == {
        ATTR_FRIENDLY_NAME: "Bedroom",
        ATTR_SUPPORTED_COLOR_MODES: [ColorMode.RGBW],
        ATTR_SUPPORTED_FEATURES: 0,
    }

    mock_light.get_color.side_effect = None
    mock_light.get_color.return_value = (80, 160, 255, 0)
    utcnow = utcnow + SCAN_INTERVAL
    async_fire_time_changed(hass, utcnow)
    await hass.async_block_till_done()

    state = hass.states.get("light.bedroom")
    assert state.state == STATE_ON
    assert dict(state.attributes) == {
        ATTR_FRIENDLY_NAME: "Bedroom",
        ATTR_SUPPORTED_COLOR_MODES: [ColorMode.RGBW],
        ATTR_SUPPORTED_FEATURES: 0,
        ATTR_COLOR_MODE: ColorMode.RGBW,
        ATTR_BRIGHTNESS: 255,
        ATTR_HS_COLOR: (pytest.approx(212.571), pytest.approx(68.627)),
        ATTR_RGB_COLOR: (80, 160, 255),
        ATTR_RGBW_COLOR: (80, 160, 255, 0),
        ATTR_XY_COLOR: (pytest.approx(0.17), pytest.approx(0.193)),
    }

    mock_light.get_color.side_effect = None
    mock_light.get_color.return_value = (80, 160, 200, 255)
    utcnow = utcnow + SCAN_INTERVAL
    async_fire_time_changed(hass, utcnow)
    await hass.async_block_till_done()

    state = hass.states.get("light.bedroom")
    assert state.state == STATE_ON
    assert dict(state.attributes) == {
        ATTR_FRIENDLY_NAME: "Bedroom",
        ATTR_SUPPORTED_COLOR_MODES: [ColorMode.RGBW],
        ATTR_SUPPORTED_FEATURES: 0,
        ATTR_COLOR_MODE: ColorMode.RGBW,
        ATTR_BRIGHTNESS: 255,
        ATTR_HS_COLOR: (pytest.approx(199.701), pytest.approx(26.275)),
        ATTR_RGB_COLOR: (188, 233, 255),
        ATTR_RGBW_COLOR: (80, 160, 200, 255),
        ATTR_XY_COLOR: (pytest.approx(0.259), pytest.approx(0.306)),
    }

    mock_light.get_color.side_effect = None
    mock_light.get_color.return_value = (80, 160, 200, 240)
    utcnow = utcnow + SCAN_INTERVAL
    async_fire_time_changed(hass, utcnow)
    await hass.async_block_till_done()

    state = hass.states.get("light.bedroom")
    assert state.state == STATE_ON
    assert dict(state.attributes) == {
        ATTR_FRIENDLY_NAME: "Bedroom",
        ATTR_SUPPORTED_COLOR_MODES: [ColorMode.RGBW],
        ATTR_SUPPORTED_FEATURES: 0,
        ATTR_COLOR_MODE: ColorMode.RGBW,
        ATTR_BRIGHTNESS: 240,
        ATTR_HS_COLOR: (pytest.approx(200.0), pytest.approx(27.059)),
        ATTR_RGB_COLOR: (186, 232, 255),
        ATTR_RGBW_COLOR: (85, 170, 212, 255),
        ATTR_XY_COLOR: (pytest.approx(0.257), pytest.approx(0.305)),
    }
