"""Tests for the integration of a twinly device."""

from datetime import timedelta
from typing import Any
from unittest.mock import AsyncMock, patch

from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from ttls.client import TwinklyError

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_EFFECT,
    ATTR_RGB_COLOR,
    ATTR_RGBW_COLOR,
    DOMAIN as LIGHT_DOMAIN,
    LightEntityFeature,
)
from homeassistant.components.twinkly import DOMAIN
from homeassistant.const import (
    ATTR_ENTITY_ID,
    ATTR_SUPPORTED_FEATURES,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
    STATE_OFF,
    STATE_UNAVAILABLE,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er

from . import setup_integration
from .const import TEST_MAC

from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform


@pytest.mark.usefixtures("mock_twinkly_client")
async def test_entities(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    entity_registry: er.EntityRegistry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test the created entities."""
    with patch("homeassistant.components.twinkly.PLATFORMS", [Platform.LIGHT]):
        await setup_integration(hass, mock_config_entry)

    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)


async def test_turn_on_off(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service."""
    mock_twinkly_client.is_on.return_value = False

    await setup_integration(hass, mock_config_entry)

    assert hass.states.get("light.tree_1").state == STATE_OFF

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1"},
        blocking=True,
    )

    mock_twinkly_client.turn_on.assert_called_once_with()


async def test_turn_on_with_brightness(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service with a brightness parameter."""
    mock_twinkly_client.is_on.return_value = False

    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_BRIGHTNESS: 255},
        blocking=True,
    )

    mock_twinkly_client.set_brightness.assert_called_once_with(100)
    mock_twinkly_client.turn_on.assert_called_once_with()


async def test_brightness_to_zero(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service with a brightness parameter."""
    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_BRIGHTNESS: 1},
        blocking=True,
    )

    mock_twinkly_client.set_brightness.assert_not_called()
    mock_twinkly_client.turn_off.assert_called_once_with()


async def test_turn_on_with_color_rgbw(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service with a rgbw parameter."""
    mock_twinkly_client.is_on.return_value = False
    mock_twinkly_client.get_details.return_value["led_profile"] = "RGBW"

    await setup_integration(hass, mock_config_entry)
    assert (
        LightEntityFeature.EFFECT
        & hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
    )

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={
            ATTR_ENTITY_ID: "light.tree_1",
            ATTR_RGBW_COLOR: (128, 64, 32, 0),
        },
        blocking=True,
    )

    mock_twinkly_client.interview.assert_called_once_with()
    mock_twinkly_client.set_static_colour.assert_called_once_with((0, 128, 64, 32))
    mock_twinkly_client.set_mode.assert_called_once_with("color")
    assert mock_twinkly_client.default_mode == "color"


async def test_turn_on_with_color_rgb(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service with a rgb parameter."""
    mock_twinkly_client.is_on.return_value = False
    mock_twinkly_client.get_details.return_value["led_profile"] = "RGB"

    await setup_integration(hass, mock_config_entry)
    assert (
        LightEntityFeature.EFFECT
        & hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
    )

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_RGB_COLOR: (128, 64, 32)},
        blocking=True,
    )

    mock_twinkly_client.interview.assert_called_once_with()
    mock_twinkly_client.set_static_colour.assert_called_once_with((128, 64, 32))
    mock_twinkly_client.set_mode.assert_called_once_with("color")
    assert mock_twinkly_client.default_mode == "color"


async def test_turn_on_with_effect(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service with effects."""
    mock_twinkly_client.is_on.return_value = False
    mock_twinkly_client.get_details.return_value["led_profile"] = "RGB"

    await setup_integration(hass, mock_config_entry)
    assert (
        LightEntityFeature.EFFECT
        & hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
    )

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_EFFECT: "2 Rainbow"},
        blocking=True,
    )

    mock_twinkly_client.interview.assert_called_once_with()
    mock_twinkly_client.set_current_movie.assert_called_once_with(2)
    mock_twinkly_client.set_mode.assert_called_once_with("movie")
    assert mock_twinkly_client.default_mode == "movie"


@pytest.mark.parametrize(
    ("data"),
    [
        {ATTR_RGBW_COLOR: (128, 64, 32, 0)},
        {ATTR_RGB_COLOR: (128, 64, 32)},
    ],
)
async def test_turn_on_with_missing_effect(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
    data: dict[str, Any],
) -> None:
    """Test light.turn_on with rgbw color and missing effect support."""
    mock_twinkly_client.is_on.return_value = False
    mock_twinkly_client.get_firmware_version.return_value["version"] = "2.7.0"

    await setup_integration(hass, mock_config_entry)
    assert (
        LightEntityFeature.EFFECT
        ^ hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
    )

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1"} | data,
        blocking=True,
    )

    mock_twinkly_client.interview.assert_called_once_with()
    mock_twinkly_client.set_cycle_colours.assert_called_once_with((128, 64, 32))
    mock_twinkly_client.set_mode.assert_called_once_with("movie")
    assert mock_twinkly_client.default_mode == "movie"
    mock_twinkly_client.set_current_movie.assert_not_called()


async def test_turn_on_with_color_rgbw_and_missing_effect(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_on service with missing effect support."""
    mock_twinkly_client.is_on.return_value = False
    mock_twinkly_client.get_firmware_version.return_value["version"] = "2.7.0"

    await setup_integration(hass, mock_config_entry)
    assert (
        LightEntityFeature.EFFECT
        ^ hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
    )

    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_ON,
        service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_EFFECT: "2 Rainbow"},
        blocking=True,
    )

    mock_twinkly_client.set_current_movie.assert_not_called()


async def test_turn_off(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Test support of the light.turn_off service."""

    await setup_integration(hass, mock_config_entry)
    await hass.services.async_call(
        LIGHT_DOMAIN,
        SERVICE_TURN_OFF,
        service_data={ATTR_ENTITY_ID: "light.tree_1"},
        blocking=True,
    )
    mock_twinkly_client.turn_off.assert_called_once_with()


async def test_no_current_movie(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test handling of missing current movie data."""

    await setup_integration(hass, mock_config_entry)

    assert hass.states.get("light.tree_1").attributes[ATTR_EFFECT] == "1 Rainbow"

    mock_twinkly_client.get_current_movie.side_effect = TwinklyError

    freezer.tick(timedelta(seconds=30))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    assert hass.states.get("light.tree_1").state != STATE_UNAVAILABLE
    assert hass.states.get("light.tree_1").attributes[ATTR_EFFECT] is None


async def test_update_name(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    freezer: FrozenDateTimeFactory,
    mock_config_entry: MockConfigEntry,
    mock_twinkly_client: AsyncMock,
) -> None:
    """Validate device's name update behavior.

    Validate that if device name is changed from the Twinkly app,
    then the name of the entity is updated and it's also persisted,
    so it can be restored when starting HA while Twinkly is offline.
    """

    await setup_integration(hass, mock_config_entry)

    dev_entry = device_registry.async_get_device({(DOMAIN, TEST_MAC)})

    assert dev_entry.name == "Tree 1"

    mock_twinkly_client.get_details.return_value["device_name"] = "new_device_name"

    freezer.tick(timedelta(seconds=30))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    dev_entry = device_registry.async_get_device({(DOMAIN, TEST_MAC)})

    assert dev_entry.name == "new_device_name"
