"""The tests for the Universal Media player platform."""

from copy import copy
from unittest.mock import Mock, patch

import pytest
from voluptuous.error import MultipleInvalid

from homeassistant import config as hass_config
from homeassistant.components import input_number, input_select, media_player, switch
from homeassistant.components.media_player import MediaClass, MediaPlayerEntityFeature
from homeassistant.components.media_player.browse_media import BrowseMedia
import homeassistant.components.universal.media_player as universal
from homeassistant.const import (
    SERVICE_RELOAD,
    STATE_OFF,
    STATE_ON,
    STATE_PAUSED,
    STATE_PLAYING,
    STATE_UNKNOWN,
)
from homeassistant.core import Context, HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.setup import async_setup_component

from tests.common import async_mock_service, get_fixture_path

CONFIG_CHILDREN_ONLY = {
    "name": "test",
    "platform": "universal",
    "children": [
        media_player.ENTITY_ID_FORMAT.format("mock1"),
        media_player.ENTITY_ID_FORMAT.format("mock2"),
    ],
}

MOCK_BROWSE_MEDIA = BrowseMedia(
    media_class=MediaClass.APP,
    media_content_id="mock-id",
    media_content_type="mock-type",
    title="Mock Title",
    can_play=False,
    can_expand=True,
)


def validate_config(config):
    """Use the platform schema to validate configuration."""
    validated_config = universal.PLATFORM_SCHEMA(config)
    validated_config.pop("platform")
    return validated_config


class MockMediaPlayer(media_player.MediaPlayerEntity):
    """Mock media player for testing."""

    def __init__(self, hass, name):
        """Initialize the media player."""
        self.hass = hass
        self._name = name
        self.entity_id = media_player.ENTITY_ID_FORMAT.format(name)
        self._state = STATE_OFF
        self._volume_level = 0
        self._is_volume_muted = False
        self._media_title = None
        self._supported_features = 0
        self._source = None
        self._tracks = 12
        self._media_image_url = None
        self._shuffle = False
        self._sound_mode = None

        self.service_calls = {
            "turn_on": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_TURN_ON
            ),
            "turn_off": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF
            ),
            "mute_volume": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_MUTE
            ),
            "set_volume_level": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET
            ),
            "media_play": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY
            ),
            "media_pause": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE
            ),
            "media_stop": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_STOP
            ),
            "media_previous_track": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PREVIOUS_TRACK
            ),
            "media_next_track": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_NEXT_TRACK
            ),
            "media_seek": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK
            ),
            "play_media": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_PLAY_MEDIA
            ),
            "volume_up": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_UP
            ),
            "volume_down": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_DOWN
            ),
            "media_play_pause": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY_PAUSE
            ),
            "select_sound_mode": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOUND_MODE
            ),
            "select_source": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE
            ),
            "toggle": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_TOGGLE
            ),
            "clear_playlist": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_CLEAR_PLAYLIST
            ),
            "repeat_set": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_REPEAT_SET
            ),
            "shuffle_set": async_mock_service(
                hass, media_player.DOMAIN, media_player.SERVICE_SHUFFLE_SET
            ),
        }

    @property
    def name(self):
        """Return the name of player."""
        return self._name

    @property
    def state(self):
        """Return the state of the player."""
        return self._state

    @property
    def volume_level(self):
        """Return the volume level of player."""
        return self._volume_level

    @property
    def is_volume_muted(self):
        """Return true if the media player is muted."""
        return self._is_volume_muted

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return MediaPlayerEntityFeature(self._supported_features)

    @property
    def media_image_url(self):
        """Image url of current playing media."""
        return self._media_image_url

    @property
    def shuffle(self):
        """Return true if the media player is shuffling."""
        return self._shuffle

    def turn_on(self):
        """Mock turn_on function."""
        self._state = None

    def turn_off(self):
        """Mock turn_off function."""
        self._state = STATE_OFF

    def mute_volume(self, mute):
        """Mock mute function."""
        self._is_volume_muted = mute

    def set_volume_level(self, volume):
        """Mock set volume level."""
        self._volume_level = volume

    def media_play(self):
        """Mock play."""
        self._state = STATE_PLAYING

    def media_pause(self):
        """Mock pause."""
        self._state = STATE_PAUSED

    def select_sound_mode(self, sound_mode):
        """Set the sound mode."""
        self._sound_mode = sound_mode

    def select_source(self, source):
        """Set the input source."""
        self._source = source

    def async_toggle(self):
        """Toggle the power on the media player."""
        self._state = STATE_OFF if self._state == STATE_ON else STATE_ON

    def clear_playlist(self):
        """Clear players playlist."""
        self._tracks = 0

    def set_shuffle(self, shuffle):
        """Enable/disable shuffle mode."""
        self._shuffle = shuffle

    def set_repeat(self, repeat):
        """Enable/disable repeat mode."""
        self._repeat = repeat


@pytest.fixture
async def mock_states(hass):
    """Set mock states used in tests."""
    result = Mock()

    result.mock_mp_1 = MockMediaPlayer(hass, "mock1")
    result.mock_mp_1.async_schedule_update_ha_state()

    result.mock_mp_2 = MockMediaPlayer(hass, "mock2")
    result.mock_mp_2.async_schedule_update_ha_state()

    await hass.async_block_till_done()

    result.mock_mute_switch_id = switch.ENTITY_ID_FORMAT.format("mute")
    hass.states.async_set(result.mock_mute_switch_id, STATE_OFF)

    result.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format("state")
    hass.states.async_set(result.mock_state_switch_id, STATE_OFF)

    result.mock_volume_id = f"{input_number.DOMAIN}.volume_level"
    hass.states.async_set(result.mock_volume_id, 0)

    result.mock_source_list_id = f"{input_select.DOMAIN}.source_list"
    hass.states.async_set(result.mock_source_list_id, ["dvd", "htpc"])

    result.mock_source_id = f"{input_select.DOMAIN}.source"
    hass.states.async_set(result.mock_source_id, "dvd")

    result.mock_sound_mode_list_id = f"{input_select.DOMAIN}.sound_mode_list"
    hass.states.async_set(result.mock_sound_mode_list_id, ["music", "movie"])

    result.mock_sound_mode_id = f"{input_select.DOMAIN}.sound_mode"
    hass.states.async_set(result.mock_sound_mode_id, "music")

    result.mock_shuffle_switch_id = switch.ENTITY_ID_FORMAT.format("shuffle")
    hass.states.async_set(result.mock_shuffle_switch_id, STATE_OFF)

    result.mock_repeat_switch_id = switch.ENTITY_ID_FORMAT.format("repeat")
    hass.states.async_set(result.mock_repeat_switch_id, STATE_OFF)

    return result


@pytest.fixture
def config_children_and_attr(mock_states):
    """Return configuration that references the mock states."""
    return {
        "name": "test",
        "platform": "universal",
        "children": [
            media_player.ENTITY_ID_FORMAT.format("mock1"),
            media_player.ENTITY_ID_FORMAT.format("mock2"),
        ],
        "attributes": {
            "is_volume_muted": mock_states.mock_mute_switch_id,
            "volume_level": mock_states.mock_volume_id,
            "source": mock_states.mock_source_id,
            "source_list": mock_states.mock_source_list_id,
            "state": mock_states.mock_state_switch_id,
            "shuffle": mock_states.mock_shuffle_switch_id,
            "repeat": mock_states.mock_repeat_switch_id,
            "sound_mode_list": mock_states.mock_sound_mode_list_id,
            "sound_mode": mock_states.mock_sound_mode_id,
        },
    }


async def test_config_children_only(hass: HomeAssistant) -> None:
    """Check config with only children."""
    config_start = copy(CONFIG_CHILDREN_ONLY)
    del config_start["platform"]
    config_start["commands"] = {}
    config_start["attributes"] = {}

    config = validate_config(CONFIG_CHILDREN_ONLY)
    assert config_start == config


async def test_config_children_and_attr(
    hass: HomeAssistant, config_children_and_attr
) -> None:
    """Check config with children and attributes."""
    config_start = copy(config_children_and_attr)
    del config_start["platform"]
    config_start["commands"] = {}

    config = validate_config(config_children_and_attr)
    assert config_start == config


async def test_config_no_name(hass: HomeAssistant) -> None:
    """Check config with no Name entry."""
    response = True
    try:
        validate_config({"platform": "universal"})
    except MultipleInvalid:
        response = False
    assert not response


async def test_config_bad_children(hass: HomeAssistant) -> None:
    """Check config with bad children entry."""
    config_no_children = {"name": "test", "platform": "universal"}
    config_bad_children = {"name": "test", "children": {}, "platform": "universal"}

    config_no_children = validate_config(config_no_children)
    assert [] == config_no_children["children"]

    config_bad_children = validate_config(config_bad_children)
    assert [] == config_bad_children["children"]


async def test_config_bad_commands(hass: HomeAssistant) -> None:
    """Check config with bad commands entry."""
    config = {"name": "test", "platform": "universal"}

    config = validate_config(config)
    assert {} == config["commands"]


async def test_config_bad_attributes(hass: HomeAssistant) -> None:
    """Check config with bad attributes."""
    config = {"name": "test", "platform": "universal"}

    config = validate_config(config)
    assert {} == config["attributes"]


async def test_config_bad_key(hass: HomeAssistant) -> None:
    """Check config with bad key."""
    config = {"name": "test", "asdf": 5, "platform": "universal"}

    config = validate_config(config)
    assert "asdf" not in config


async def test_platform_setup(hass: HomeAssistant) -> None:
    """Test platform setup."""
    config = {"name": "test", "platform": "universal"}
    bad_config = {"platform": "universal"}
    entities = []

    def add_entities(new_entities):
        """Add devices to list."""
        entities.extend(new_entities)

    setup_ok = True
    try:
        await universal.async_setup_platform(
            hass, validate_config(bad_config), add_entities
        )
    except MultipleInvalid:
        setup_ok = False
    assert not setup_ok
    assert len(entities) == 0

    await universal.async_setup_platform(hass, validate_config(config), add_entities)
    assert len(entities) == 1
    assert entities[0].name == "test"


async def test_master_state(hass: HomeAssistant) -> None:
    """Test master state property."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.master_state is None


async def test_master_state_with_attrs(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test master state property."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.master_state == STATE_OFF
    hass.states.async_set(mock_states.mock_state_switch_id, STATE_ON)
    assert ump.master_state == STATE_ON


async def test_master_state_with_bad_attrs(
    hass: HomeAssistant, config_children_and_attr
) -> None:
    """Test master state property."""
    config = copy(config_children_and_attr)
    config["attributes"]["state"] = "bad.entity_id"
    config = validate_config(config)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.master_state == STATE_OFF


async def test_active_child_state(hass: HomeAssistant, mock_states) -> None:
    """Test active child state property."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert ump._child_state is None

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert mock_states.mock_mp_1.entity_id == ump._child_state.entity_id

    mock_states.mock_mp_2._state = STATE_PLAYING
    mock_states.mock_mp_2.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert mock_states.mock_mp_1.entity_id == ump._child_state.entity_id

    mock_states.mock_mp_1._state = STATE_PAUSED
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert mock_states.mock_mp_2.entity_id == ump._child_state.entity_id

    mock_states.mock_mp_1._state = STATE_OFF
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert mock_states.mock_mp_2.entity_id == ump._child_state.entity_id

    mock_states.mock_mp_1._state = "invalid_state"
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert mock_states.mock_mp_2.entity_id == ump._child_state.entity_id


async def test_name(hass: HomeAssistant) -> None:
    """Test name property."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert config["name"] == ump.name


async def test_polling(hass: HomeAssistant) -> None:
    """Test should_poll property."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.should_poll is False


async def test_state_children_only(hass: HomeAssistant, mock_states) -> None:
    """Test media player state with only children."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert ump.state, STATE_OFF

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.state == STATE_PLAYING

    mock_states.mock_mp_1._state = STATE_ON
    mock_states.mock_mp_1._attr_assumed_state = True
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.assumed_state is True


async def test_state_with_children_and_attrs(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test media player with children and master state."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert ump.state == STATE_OFF

    hass.states.async_set(mock_states.mock_state_switch_id, STATE_ON)
    await ump.async_update()
    assert ump.state == STATE_ON

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.state == STATE_PLAYING

    hass.states.async_set(mock_states.mock_state_switch_id, STATE_OFF)
    await ump.async_update()
    assert ump.state == STATE_OFF


async def test_volume_level(hass: HomeAssistant, mock_states) -> None:
    """Test volume level property."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert ump.volume_level is None

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.volume_level == 0

    mock_states.mock_mp_1._volume_level = 1
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.volume_level == 1


async def test_media_image_url(hass: HomeAssistant, mock_states) -> None:
    """Test media_image_url property."""
    test_url = "test_url"
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert ump.media_image_url is None

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1._media_image_url = test_url
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    # mock_mp_1 will convert the url to the api proxy url. This test
    # ensures ump passes through the same url without an additional proxy.
    assert mock_states.mock_mp_1.entity_picture == ump.entity_picture


async def test_is_volume_muted_children_only(hass: HomeAssistant, mock_states) -> None:
    """Test is volume muted property w/ children only."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert not ump.is_volume_muted

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert not ump.is_volume_muted

    mock_states.mock_mp_1._is_volume_muted = True
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.is_volume_muted


async def test_sound_mode_list_children_and_attr(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test sound mode list property w/ children and attrs."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.sound_mode_list == "['music', 'movie']"

    hass.states.async_set(
        mock_states.mock_sound_mode_list_id, ["music", "movie", "game"]
    )
    assert ump.sound_mode_list == "['music', 'movie', 'game']"


async def test_source_list_children_and_attr(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test source list property w/ children and attrs."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.source_list == "['dvd', 'htpc']"

    hass.states.async_set(mock_states.mock_source_list_id, ["dvd", "htpc", "game"])
    assert ump.source_list == "['dvd', 'htpc', 'game']"


async def test_sound_mode_children_and_attr(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test sound modeproperty w/ children and attrs."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.sound_mode == "music"

    hass.states.async_set(mock_states.mock_sound_mode_id, "movie")
    assert ump.sound_mode == "movie"


async def test_source_children_and_attr(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test source property w/ children and attrs."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.source == "dvd"

    hass.states.async_set(mock_states.mock_source_id, "htpc")
    assert ump.source == "htpc"


async def test_volume_level_children_and_attr(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test volume level property w/ children and attrs."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert ump.volume_level == 0

    hass.states.async_set(mock_states.mock_volume_id, 100)
    assert ump.volume_level == 100


async def test_is_volume_muted_children_and_attr(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test is volume muted property w/ children and attrs."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)

    assert not ump.is_volume_muted

    hass.states.async_set(mock_states.mock_mute_switch_id, STATE_ON)
    assert ump.is_volume_muted


async def test_supported_features_children_only(
    hass: HomeAssistant, mock_states
) -> None:
    """Test supported media commands with only children."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    assert ump.supported_features == 0

    mock_states.mock_mp_1._supported_features = 512
    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()
    assert ump.supported_features == 512


async def test_supported_features_children_and_cmds(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test supported media commands with children and attrs."""
    config = copy(config_children_and_attr)
    excmd = {"service": "media_player.test", "data": {}}
    config["commands"] = {
        "turn_on": excmd,
        "turn_off": excmd,
        "volume_up": excmd,
        "volume_down": excmd,
        "volume_mute": excmd,
        "volume_set": excmd,
        "select_sound_mode": excmd,
        "select_source": excmd,
        "repeat_set": excmd,
        "shuffle_set": excmd,
        "media_play": excmd,
        "media_pause": excmd,
        "media_stop": excmd,
        "media_next_track": excmd,
        "media_previous_track": excmd,
        "toggle": excmd,
        "play_media": excmd,
        "clear_playlist": excmd,
    }
    config["browse_media_entity"] = "media_player.test"
    config = validate_config(config)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()

    check_flags = (
        MediaPlayerEntityFeature.TURN_ON
        | MediaPlayerEntityFeature.TURN_OFF
        | MediaPlayerEntityFeature.VOLUME_STEP
        | MediaPlayerEntityFeature.VOLUME_MUTE
        | MediaPlayerEntityFeature.SELECT_SOUND_MODE
        | MediaPlayerEntityFeature.SELECT_SOURCE
        | MediaPlayerEntityFeature.REPEAT_SET
        | MediaPlayerEntityFeature.SHUFFLE_SET
        | MediaPlayerEntityFeature.VOLUME_SET
        | MediaPlayerEntityFeature.PLAY
        | MediaPlayerEntityFeature.PAUSE
        | MediaPlayerEntityFeature.STOP
        | MediaPlayerEntityFeature.NEXT_TRACK
        | MediaPlayerEntityFeature.PREVIOUS_TRACK
        | MediaPlayerEntityFeature.PLAY_MEDIA
        | MediaPlayerEntityFeature.CLEAR_PLAYLIST
        | MediaPlayerEntityFeature.BROWSE_MEDIA
    )

    assert check_flags == ump.supported_features


async def test_overrides(hass: HomeAssistant, config_children_and_attr) -> None:
    """Test overrides."""
    config = copy(config_children_and_attr)
    excmd = {"service": "test.override", "data": {}}
    config["name"] = "overridden"
    config["commands"] = {
        "turn_on": excmd,
        "turn_off": excmd,
        "volume_up": excmd,
        "volume_down": excmd,
        "volume_mute": excmd,
        "volume_set": excmd,
        "select_sound_mode": excmd,
        "select_source": excmd,
        "repeat_set": excmd,
        "shuffle_set": excmd,
        "media_play": excmd,
        "media_play_pause": excmd,
        "media_pause": excmd,
        "media_stop": excmd,
        "media_next_track": excmd,
        "media_previous_track": excmd,
        "clear_playlist": excmd,
        "play_media": excmd,
        "toggle": excmd,
    }
    await async_setup_component(hass, "media_player", {"media_player": config})
    await hass.async_block_till_done()

    service = async_mock_service(hass, "test", "override")
    await hass.services.async_call(
        "media_player",
        "turn_on",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 1
    await hass.services.async_call(
        "media_player",
        "turn_off",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 2
    await hass.services.async_call(
        "media_player",
        "volume_up",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 3
    await hass.services.async_call(
        "media_player",
        "volume_down",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 4
    await hass.services.async_call(
        "media_player",
        "volume_mute",
        service_data={
            "entity_id": "media_player.overridden",
            "is_volume_muted": True,
        },
        blocking=True,
    )
    assert len(service) == 5
    await hass.services.async_call(
        "media_player",
        "volume_set",
        service_data={"entity_id": "media_player.overridden", "volume_level": 1},
        blocking=True,
    )
    assert len(service) == 6
    await hass.services.async_call(
        "media_player",
        "select_sound_mode",
        service_data={
            "entity_id": "media_player.overridden",
            "sound_mode": "music",
        },
        blocking=True,
    )
    assert len(service) == 7
    await hass.services.async_call(
        "media_player",
        "select_source",
        service_data={"entity_id": "media_player.overridden", "source": "video1"},
        blocking=True,
    )
    assert len(service) == 8
    await hass.services.async_call(
        "media_player",
        "repeat_set",
        service_data={"entity_id": "media_player.overridden", "repeat": "all"},
        blocking=True,
    )
    assert len(service) == 9
    await hass.services.async_call(
        "media_player",
        "shuffle_set",
        service_data={"entity_id": "media_player.overridden", "shuffle": True},
        blocking=True,
    )
    assert len(service) == 10
    await hass.services.async_call(
        "media_player",
        "media_play",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 11
    await hass.services.async_call(
        "media_player",
        "media_pause",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 12
    await hass.services.async_call(
        "media_player",
        "media_stop",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 13
    await hass.services.async_call(
        "media_player",
        "media_next_track",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 14
    await hass.services.async_call(
        "media_player",
        "media_previous_track",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 15
    await hass.services.async_call(
        "media_player",
        "clear_playlist",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 16
    await hass.services.async_call(
        "media_player",
        "media_play_pause",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 17
    await hass.services.async_call(
        "media_player",
        "play_media",
        service_data={
            "entity_id": "media_player.overridden",
            "media_content_id": 1,
            "media_content_type": "channel",
        },
        blocking=True,
    )
    assert len(service) == 18
    await hass.services.async_call(
        "media_player",
        "toggle",
        service_data={"entity_id": "media_player.overridden"},
        blocking=True,
    )
    assert len(service) == 19


async def test_supported_features_play_pause(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test supported media commands with play_pause function."""
    config = copy(config_children_and_attr)
    excmd = {"service": "media_player.test", "data": {"entity_id": "test"}}
    config["commands"] = {"media_play_pause": excmd}
    config = validate_config(config)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    mock_states.mock_mp_1._state = STATE_PLAYING
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()

    check_flags = MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE

    assert check_flags == ump.supported_features


async def test_service_call_no_active_child(
    hass: HomeAssistant, config_children_and_attr, mock_states
) -> None:
    """Test a service call to children with no active child."""
    config = validate_config(config_children_and_attr)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    mock_states.mock_mp_1._state = STATE_OFF
    mock_states.mock_mp_1.async_schedule_update_ha_state()
    mock_states.mock_mp_2._state = STATE_OFF
    mock_states.mock_mp_2.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()

    await ump.async_turn_off()
    assert len(mock_states.mock_mp_1.service_calls["turn_off"]) == 0
    assert len(mock_states.mock_mp_2.service_calls["turn_off"]) == 0


async def test_service_call_to_child(hass: HomeAssistant, mock_states) -> None:
    """Test service calls that should be routed to a child."""
    config = validate_config(CONFIG_CHILDREN_ONLY)

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    mock_states.mock_mp_2._state = STATE_PLAYING
    mock_states.mock_mp_2.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()

    await ump.async_turn_off()
    assert len(mock_states.mock_mp_2.service_calls["turn_off"]) == 1

    await ump.async_turn_on()
    assert len(mock_states.mock_mp_2.service_calls["turn_on"]) == 1

    await ump.async_mute_volume(True)
    assert len(mock_states.mock_mp_2.service_calls["mute_volume"]) == 1

    await ump.async_set_volume_level(0.5)
    assert len(mock_states.mock_mp_2.service_calls["set_volume_level"]) == 1

    await ump.async_media_play()
    assert len(mock_states.mock_mp_2.service_calls["media_play"]) == 1

    await ump.async_media_pause()
    assert len(mock_states.mock_mp_2.service_calls["media_pause"]) == 1

    await ump.async_media_stop()
    assert len(mock_states.mock_mp_2.service_calls["media_stop"]) == 1

    await ump.async_media_previous_track()
    assert len(mock_states.mock_mp_2.service_calls["media_previous_track"]) == 1

    await ump.async_media_next_track()
    assert len(mock_states.mock_mp_2.service_calls["media_next_track"]) == 1

    await ump.async_media_seek(100)
    assert len(mock_states.mock_mp_2.service_calls["media_seek"]) == 1

    await ump.async_play_media("movie", "batman")
    assert len(mock_states.mock_mp_2.service_calls["play_media"]) == 1

    await ump.async_volume_up()
    assert len(mock_states.mock_mp_2.service_calls["volume_up"]) == 1

    await ump.async_volume_down()
    assert len(mock_states.mock_mp_2.service_calls["volume_down"]) == 1

    await ump.async_media_play_pause()
    assert len(mock_states.mock_mp_2.service_calls["media_play_pause"]) == 1

    await ump.async_select_sound_mode("music")
    assert len(mock_states.mock_mp_2.service_calls["select_sound_mode"]) == 1

    await ump.async_select_source("dvd")
    assert len(mock_states.mock_mp_2.service_calls["select_source"]) == 1

    await ump.async_clear_playlist()
    assert len(mock_states.mock_mp_2.service_calls["clear_playlist"]) == 1

    await ump.async_set_repeat(True)
    assert len(mock_states.mock_mp_2.service_calls["repeat_set"]) == 1

    await ump.async_set_shuffle(True)
    assert len(mock_states.mock_mp_2.service_calls["shuffle_set"]) == 1

    await ump.async_toggle()
    # Delegate to turn_off
    assert len(mock_states.mock_mp_2.service_calls["turn_off"]) == 2


async def test_service_call_to_command(hass: HomeAssistant, mock_states) -> None:
    """Test service call to command."""
    config = copy(CONFIG_CHILDREN_ONLY)
    config["commands"] = {"turn_off": {"service": "test.turn_off", "data": {}}}
    config = validate_config(config)

    service = async_mock_service(hass, "test", "turn_off")

    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    mock_states.mock_mp_2._state = STATE_PLAYING
    mock_states.mock_mp_2.async_schedule_update_ha_state()
    await hass.async_block_till_done()
    await ump.async_update()

    await ump.async_turn_off()
    assert len(service) == 1


async def test_state_template(hass: HomeAssistant) -> None:
    """Test with a simple valid state template."""
    hass.states.async_set("sensor.test_sensor", STATE_ON)

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "state_template": "{{ states.sensor.test_sensor.state }}",
            }
        },
    )
    await hass.async_block_till_done()
    assert len(hass.states.async_all()) == 2
    await hass.async_start()

    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_ON
    hass.states.async_set("sensor.test_sensor", STATE_OFF)
    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_OFF


async def test_browse_media(hass: HomeAssistant) -> None:
    """Test browse media."""
    await async_setup_component(hass, "homeassistant", {})
    await async_setup_component(
        hass, "media_player", {"media_player": {"platform": "demo"}}
    )
    await hass.async_block_till_done()

    config = {
        "name": "test",
        "platform": "universal",
        "children": [
            "media_player.bedroom",
        ],
    }
    config = validate_config(config)
    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    with (
        patch(
            "homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features",
            MediaPlayerEntityFeature.BROWSE_MEDIA,
        ),
        patch(
            "homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media",
            return_value=MOCK_BROWSE_MEDIA,
        ),
    ):
        result = await ump.async_browse_media()
        assert result == MOCK_BROWSE_MEDIA


async def test_browse_media_override(hass: HomeAssistant) -> None:
    """Test browse media override."""
    await async_setup_component(hass, "homeassistant", {})
    await async_setup_component(
        hass, "media_player", {"media_player": {"platform": "demo"}}
    )
    await hass.async_block_till_done()

    config = {
        "name": "test",
        "platform": "universal",
        "children": [
            "media_player.mock1",
        ],
        "browse_media_entity": "media_player.bedroom",
    }
    config = validate_config(config)
    ump = universal.UniversalMediaPlayer(hass, config)
    ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
    await ump.async_update()

    with (
        patch(
            "homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features",
            MediaPlayerEntityFeature.BROWSE_MEDIA,
        ),
        patch(
            "homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media",
            return_value=MOCK_BROWSE_MEDIA,
        ),
    ):
        result = await ump.async_browse_media()
        assert result == MOCK_BROWSE_MEDIA


async def test_device_class(hass: HomeAssistant) -> None:
    """Test device_class property."""
    hass.states.async_set("sensor.test_sensor", "on")

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "device_class": "tv",
            }
        },
    )
    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").attributes["device_class"] == "tv"


async def test_unique_id(
    hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
    """Test unique_id property."""
    hass.states.async_set("sensor.test_sensor", "on")

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "unique_id": "universal_master_bed_tv",
            }
        },
    )
    await hass.async_block_till_done()
    assert (
        entity_registry.async_get("media_player.tv").unique_id
        == "universal_master_bed_tv"
    )


async def test_invalid_state_template(hass: HomeAssistant) -> None:
    """Test invalid state template sets state to None."""
    hass.states.async_set("sensor.test_sensor", "on")

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "state_template": "{{ states.sensor.test_sensor.state + x }}",
            }
        },
    )
    await hass.async_block_till_done()
    assert len(hass.states.async_all()) == 2
    await hass.async_start()

    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_UNKNOWN
    hass.states.async_set("sensor.test_sensor", "off")
    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_UNKNOWN


async def test_master_state_with_template(hass: HomeAssistant) -> None:
    """Test the state_template option."""
    hass.states.async_set("input_boolean.test", STATE_OFF)
    hass.states.async_set("media_player.mock1", STATE_OFF)

    templ = (
        '{% if states.input_boolean.test.state == "off" %}on'
        "{% else %}{{ states.media_player.mock1.state }}{% endif %}"
    )

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "state_template": templ,
            }
        },
    )

    await hass.async_block_till_done()
    assert len(hass.states.async_all()) == 3
    await hass.async_start()

    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_ON

    events = []

    async_track_state_change_event(
        hass, "media_player.tv", callback(lambda event: events.append(event))
    )

    context = Context()
    hass.states.async_set("input_boolean.test", STATE_ON, context=context)
    await hass.async_block_till_done()

    assert hass.states.get("media_player.tv").state == STATE_OFF
    assert events[0].context == context


async def test_invalid_active_child_template(hass: HomeAssistant) -> None:
    """Test invalid active child template."""
    hass.states.async_set("media_player.mock1", STATE_PLAYING)
    hass.states.async_set("media_player.mock2", STATE_PAUSED)

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "children": ["media_player.mock1", "media_player.mock2"],
                "active_child_template": "{{invalid.invalid}}!invalid",
            }
        },
    )
    await hass.async_block_till_done()
    await hass.async_start()

    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_PLAYING


async def test_active_child_template(hass: HomeAssistant) -> None:
    """Test override active child with template."""
    hass.states.async_set("media_player.mock1", STATE_PLAYING)
    hass.states.async_set("media_player.mock2", STATE_PAUSED)

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "children": ["media_player.mock1", "media_player.mock2"],
                "active_child_template": "{{ 'media_player.mock2' }}",
            }
        },
    )
    await hass.async_block_till_done()
    await hass.async_start()

    hass.states.async_set("media_player.mock2", STATE_ON)

    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_ON


async def test_reload(hass: HomeAssistant) -> None:
    """Test reloading the media player from yaml."""
    hass.states.async_set("input_boolean.test", STATE_OFF)
    hass.states.async_set("media_player.mock1", STATE_OFF)

    templ = (
        '{% if states.input_boolean.test.state == "off" %}on'
        "{% else %}{{ states.media_player.mock1.state }}{% endif %}"
    )

    await async_setup_component(
        hass,
        "media_player",
        {
            "media_player": {
                "platform": "universal",
                "name": "tv",
                "state_template": templ,
            }
        },
    )

    await hass.async_block_till_done()
    assert len(hass.states.async_all()) == 3
    await hass.async_start()

    await hass.async_block_till_done()
    assert hass.states.get("media_player.tv").state == STATE_ON

    hass.states.async_set("input_boolean.test", STATE_ON)
    await hass.async_block_till_done()

    assert hass.states.get("media_player.tv").state == STATE_OFF

    hass.states.async_set("media_player.master_bedroom_2", STATE_OFF)
    hass.states.async_set(
        "remote.alexander_master_bedroom",
        STATE_ON,
        {"activity_list": ["act1", "act2"], "current_activity": "act2"},
    )

    yaml_path = get_fixture_path("configuration.yaml", "universal")
    with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
        await hass.services.async_call(
            "universal",
            SERVICE_RELOAD,
            {},
            blocking=True,
        )
        await hass.async_block_till_done()

    assert len(hass.states.async_all()) == 5

    assert hass.states.get("media_player.tv") is None
    assert hass.states.get("media_player.master_bed_tv").state == "on"
    assert hass.states.get("media_player.master_bed_tv").attributes["source"] == "act2"
    assert (
        "device_class" not in hass.states.get("media_player.master_bed_tv").attributes
    )
    assert "unique_id" not in hass.states.get("media_player.master_bed_tv").attributes
