"""Tests for the OpenRGB select platform."""

from collections.abc import Generator
from types import SimpleNamespace
from unittest.mock import MagicMock, patch

from freezegun.api import FrozenDateTimeFactory
from openrgb.utils import OpenRGBDisconnected
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.openrgb.const import DOMAIN, SCAN_INTERVAL
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
    ATTR_ENTITY_ID,
    ATTR_OPTION,
    SERVICE_SELECT_OPTION,
    STATE_UNAVAILABLE,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import device_registry as dr, entity_registry as er

from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform


@pytest.fixture(autouse=True)
def select_only() -> Generator[None]:
    """Enable only the select platform."""
    with patch(
        "homeassistant.components.openrgb.PLATFORMS",
        [Platform.SELECT],
    ):
        yield


@pytest.fixture
def mock_profiles() -> list[SimpleNamespace]:
    """Return a list of mock profiles."""
    return [
        SimpleNamespace(name="Gaming"),
        SimpleNamespace(name="Work"),
        SimpleNamespace(name="Rainbow"),
    ]


# Test basic entity setup and configuration
@pytest.mark.usefixtures("init_integration")
async def test_entities(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    entity_registry: er.EntityRegistry,
    device_registry: dr.DeviceRegistry,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the select entities."""
    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

    # Ensure entity is correctly assigned to the OpenRGB server device
    device_entry = device_registry.async_get_device(
        identifiers={(DOMAIN, mock_config_entry.entry_id)}
    )
    assert device_entry
    entity_entries = er.async_entries_for_config_entry(
        entity_registry, mock_config_entry.entry_id
    )
    assert len(entity_entries) == 1
    assert entity_entries[0].device_id == device_entry.id


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_with_profiles(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
) -> None:
    """Test select entity with available profiles."""
    mock_openrgb_client.profiles = mock_profiles

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Verify select entity has profile options
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.attributes.get("options") == ["Gaming", "Work", "Rainbow"]


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_with_no_profiles(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
) -> None:
    """Test select entity when no profiles are available."""
    mock_openrgb_client.profiles = []

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Verify select entity is unavailable when no profiles exist
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == STATE_UNAVAILABLE
    assert state.attributes.get("options") == []


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_option_success(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
) -> None:
    """Test selecting a profile successfully."""
    mock_openrgb_client.profiles = mock_profiles

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Select a profile
    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: "select.test_computer_profile",
            ATTR_OPTION: "Gaming",
        },
        blocking=True,
    )

    # Verify load_profile was called with the correct profile name
    mock_openrgb_client.load_profile.assert_called_once_with("Gaming")

    # Verify the current option is set to the selected profile
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == "Gaming"


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_option_not_found(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
) -> None:
    """Test selecting a profile that doesn't exist."""
    mock_openrgb_client.profiles = mock_profiles

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Try to select a non-existent profile
    # The select platform will validate that the option is in the options list
    with pytest.raises(
        ServiceValidationError,
        match="Option NonExistent is not valid for entity select.test_computer_profile",
    ):
        await hass.services.async_call(
            SELECT_DOMAIN,
            SERVICE_SELECT_OPTION,
            {
                ATTR_ENTITY_ID: "select.test_computer_profile",
                ATTR_OPTION: "NonExistent",
            },
            blocking=True,
        )


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_option_connection_error(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
) -> None:
    """Test selecting a profile with connection error."""
    mock_openrgb_client.profiles = mock_profiles
    mock_openrgb_client.load_profile.side_effect = OpenRGBDisconnected(
        "Connection lost"
    )

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Try to select a profile - should raise HomeAssistantError
    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            SELECT_DOMAIN,
            SERVICE_SELECT_OPTION,
            {
                ATTR_ENTITY_ID: "select.test_computer_profile",
                ATTR_OPTION: "Gaming",
            },
            blocking=True,
        )


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_option_value_error(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
) -> None:
    """Test selecting a profile with ValueError."""
    mock_openrgb_client.profiles = mock_profiles
    mock_openrgb_client.load_profile.side_effect = ValueError("Invalid profile data")

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Try to select a profile - should raise HomeAssistantError
    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            SELECT_DOMAIN,
            SERVICE_SELECT_OPTION,
            {
                ATTR_ENTITY_ID: "select.test_computer_profile",
                ATTR_OPTION: "Gaming",
            },
            blocking=True,
        )


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_profiles_update_on_refresh(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test that profile list updates when profiles change."""
    # Start with initial profiles
    mock_openrgb_client.profiles = mock_profiles[:2]  # Only Gaming and Work

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Verify initial profile options
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.attributes.get("options") == ["Gaming", "Work"]

    # Add a new profile
    mock_openrgb_client.profiles = mock_profiles  # All three profiles

    freezer.tick(SCAN_INTERVAL)
    async_fire_time_changed(hass)
    await hass.async_block_till_done(wait_background_tasks=True)

    # Verify updated profile options
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.attributes.get("options") == ["Gaming", "Work", "Rainbow"]


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_select_becomes_unavailable_when_profiles_removed(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_profiles: list[SimpleNamespace],
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test select becomes unavailable when all profiles are removed."""
    # Start with profiles
    mock_openrgb_client.profiles = mock_profiles

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Verify select entity is available with profiles
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state != STATE_UNAVAILABLE
    assert state.attributes.get("options") == ["Gaming", "Work", "Rainbow"]

    # Remove all profiles
    mock_openrgb_client.profiles = []

    freezer.tick(SCAN_INTERVAL)
    async_fire_time_changed(hass)
    await hass.async_block_till_done(wait_background_tasks=True)

    # Verify select entity becomes unavailable
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == STATE_UNAVAILABLE
    assert state.attributes.get("options") == []


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_current_option_cleared_when_device_state_changes(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_openrgb_device: MagicMock,
    mock_profiles: list[SimpleNamespace],
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test current option is cleared when device state changes externally."""
    mock_openrgb_client.profiles = mock_profiles

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Select a profile
    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: "select.test_computer_profile",
            ATTR_OPTION: "Gaming",
        },
        blocking=True,
    )

    # Verify the current option is set
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == "Gaming"

    # Simulate external device state change by modifying active_mode
    mock_openrgb_device.active_mode = 0

    freezer.tick(SCAN_INTERVAL)
    async_fire_time_changed(hass)
    await hass.async_block_till_done(wait_background_tasks=True)

    # Verify current option is cleared because device state changed
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == "unknown"


@pytest.mark.usefixtures("mock_openrgb_client")
async def test_current_option_cleared_when_colors_change(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_openrgb_client: MagicMock,
    mock_openrgb_device: MagicMock,
    mock_profiles: list[SimpleNamespace],
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test current option is cleared when device colors change externally."""
    mock_openrgb_client.profiles = mock_profiles

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.LOADED

    # Select a profile
    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: "select.test_computer_profile",
            ATTR_OPTION: "Work",
        },
        blocking=True,
    )

    # Verify the current option is set
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == "Work"

    # Simulate external color change
    mock_openrgb_device.colors[0].red = 0
    mock_openrgb_device.colors[0].green = 255

    freezer.tick(SCAN_INTERVAL)
    async_fire_time_changed(hass)
    await hass.async_block_till_done(wait_background_tasks=True)

    # Verify current option is cleared because device colors changed
    state = hass.states.get("select.test_computer_profile")
    assert state
    assert state.state == "unknown"
