"""Tests for the Jellyfin media_player platform."""
from datetime import timedelta
from unittest.mock import MagicMock

from homeassistant.components.jellyfin.const import DOMAIN
from homeassistant.components.media_player import (
    ATTR_MEDIA_ALBUM_ARTIST,
    ATTR_MEDIA_ALBUM_NAME,
    ATTR_MEDIA_ARTIST,
    ATTR_MEDIA_CONTENT_ID,
    ATTR_MEDIA_CONTENT_TYPE,
    ATTR_MEDIA_DURATION,
    ATTR_MEDIA_EPISODE,
    ATTR_MEDIA_POSITION,
    ATTR_MEDIA_POSITION_UPDATED_AT,
    ATTR_MEDIA_SEASON,
    ATTR_MEDIA_SERIES_TITLE,
    ATTR_MEDIA_TRACK,
    ATTR_MEDIA_VOLUME_LEVEL,
    ATTR_MEDIA_VOLUME_MUTED,
    DOMAIN as MP_DOMAIN,
    MediaClass,
    MediaPlayerState,
    MediaType,
)
from homeassistant.const import (
    ATTR_DEVICE_CLASS,
    ATTR_ENTITY_ID,
    ATTR_FRIENDLY_NAME,
    ATTR_ICON,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util.dt import utcnow

from . import async_load_json_fixture

from tests.common import MockConfigEntry, async_fire_time_changed
from tests.typing import WebSocketGenerator


async def test_media_player(
    hass: HomeAssistant,
    init_integration: MockConfigEntry,
    mock_jellyfin: MagicMock,
    mock_api: MagicMock,
) -> None:
    """Test the Jellyfin media player."""
    device_registry = dr.async_get(hass)
    entity_registry = er.async_get(hass)

    state = hass.states.get("media_player.jellyfin_device")

    assert state
    assert state.state == MediaPlayerState.PAUSED
    assert state.attributes.get(ATTR_DEVICE_CLASS) is None
    assert state.attributes.get(ATTR_FRIENDLY_NAME) == "JELLYFIN-DEVICE"
    assert state.attributes.get(ATTR_ICON) is None
    assert state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 0.0
    assert state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is True
    assert state.attributes.get(ATTR_MEDIA_DURATION) == 60
    assert state.attributes.get(ATTR_MEDIA_POSITION) == 10
    assert state.attributes.get(ATTR_MEDIA_POSITION_UPDATED_AT)
    assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == "EPISODE-UUID"
    assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MediaType.TVSHOW
    assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) == "SERIES"
    assert state.attributes.get(ATTR_MEDIA_SEASON) == 1
    assert state.attributes.get(ATTR_MEDIA_EPISODE) == 3

    entry = entity_registry.async_get(state.entity_id)
    assert entry
    assert entry.device_id
    assert entry.entity_category is None
    assert entry.unique_id == "SERVER-UUID-SESSION-UUID"

    assert len(mock_api.sessions.mock_calls) == 1
    async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
    await hass.async_block_till_done()
    assert len(mock_api.sessions.mock_calls) == 2

    mock_api.sessions.return_value = []
    async_fire_time_changed(hass, utcnow() + timedelta(seconds=20))
    await hass.async_block_till_done()
    assert len(mock_api.sessions.mock_calls) == 3

    device = device_registry.async_get(entry.device_id)
    assert device
    assert device.configuration_url is None
    assert device.connections == set()
    assert device.entry_type is None
    assert device.hw_version is None
    assert device.identifiers == {(DOMAIN, "DEVICE-UUID")}
    assert device.manufacturer == "Jellyfin"
    assert device.name == "JELLYFIN-DEVICE"
    assert device.sw_version == "1.0.0"


async def test_media_player_music(
    hass: HomeAssistant,
    init_integration: MockConfigEntry,
    mock_jellyfin: MagicMock,
    mock_api: MagicMock,
) -> None:
    """Test the Jellyfin media player."""
    entity_registry = er.async_get(hass)

    state = hass.states.get("media_player.jellyfin_device_four")

    assert state
    assert state.state == MediaPlayerState.PLAYING
    assert state.attributes.get(ATTR_DEVICE_CLASS) is None
    assert state.attributes.get(ATTR_FRIENDLY_NAME) == "JELLYFIN DEVICE FOUR"
    assert state.attributes.get(ATTR_ICON) is None
    assert state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 1.0
    assert state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is False
    assert state.attributes.get(ATTR_MEDIA_DURATION) == 73
    assert state.attributes.get(ATTR_MEDIA_POSITION) == 22
    assert state.attributes.get(ATTR_MEDIA_POSITION_UPDATED_AT)
    assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == "MUSIC-UUID"
    assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MediaType.MUSIC
    assert state.attributes.get(ATTR_MEDIA_ALBUM_NAME) == "ALBUM"
    assert state.attributes.get(ATTR_MEDIA_ALBUM_ARTIST) == "Album Artist"
    assert state.attributes.get(ATTR_MEDIA_ARTIST) == "Contributing Artist"
    assert state.attributes.get(ATTR_MEDIA_TRACK) == 1
    assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) is None
    assert state.attributes.get(ATTR_MEDIA_SEASON) is None
    assert state.attributes.get(ATTR_MEDIA_EPISODE) is None

    entry = entity_registry.async_get(state.entity_id)
    assert entry
    assert entry.device_id is None
    assert entry.entity_category is None
    assert entry.unique_id == "SERVER-UUID-SESSION-UUID-FOUR"


async def test_services(
    hass: HomeAssistant,
    init_integration: MockConfigEntry,
    mock_jellyfin: MagicMock,
    mock_api: MagicMock,
) -> None:
    """Test Jellyfin media player services."""
    state = hass.states.get("media_player.jellyfin_device")
    assert state

    await hass.services.async_call(
        MP_DOMAIN,
        "play_media",
        {
            ATTR_ENTITY_ID: state.entity_id,
            "media_content_type": "",
            "media_content_id": "ITEM-UUID",
        },
        blocking=True,
    )
    assert len(mock_api.remote_play_media.mock_calls) == 1
    assert mock_api.remote_play_media.mock_calls[0].args == (
        "SESSION-UUID",
        ["ITEM-UUID"],
    )

    await hass.services.async_call(
        MP_DOMAIN,
        "media_pause",
        {
            ATTR_ENTITY_ID: state.entity_id,
        },
        blocking=True,
    )
    assert len(mock_api.remote_pause.mock_calls) == 1

    await hass.services.async_call(
        MP_DOMAIN,
        "media_play",
        {
            ATTR_ENTITY_ID: state.entity_id,
        },
        blocking=True,
    )
    assert len(mock_api.remote_unpause.mock_calls) == 1

    await hass.services.async_call(
        MP_DOMAIN,
        "media_play_pause",
        {
            ATTR_ENTITY_ID: state.entity_id,
        },
        blocking=True,
    )
    assert len(mock_api.remote_playpause.mock_calls) == 1

    await hass.services.async_call(
        MP_DOMAIN,
        "media_seek",
        {
            ATTR_ENTITY_ID: state.entity_id,
            "seek_position": 10,
        },
        blocking=True,
    )
    assert len(mock_api.remote_seek.mock_calls) == 1
    assert mock_api.remote_seek.mock_calls[0].args == (
        "SESSION-UUID",
        100000000,
    )

    await hass.services.async_call(
        MP_DOMAIN,
        "media_stop",
        {
            ATTR_ENTITY_ID: state.entity_id,
        },
        blocking=True,
    )
    assert len(mock_api.remote_stop.mock_calls) == 1

    await hass.services.async_call(
        MP_DOMAIN,
        "volume_set",
        {
            ATTR_ENTITY_ID: state.entity_id,
            "volume_level": 0.5,
        },
        blocking=True,
    )
    assert len(mock_api.remote_set_volume.mock_calls) == 1

    await hass.services.async_call(
        MP_DOMAIN,
        "volume_mute",
        {
            ATTR_ENTITY_ID: state.entity_id,
            "is_volume_muted": True,
        },
        blocking=True,
    )
    assert len(mock_api.remote_mute.mock_calls) == 1

    await hass.services.async_call(
        MP_DOMAIN,
        "volume_mute",
        {
            ATTR_ENTITY_ID: state.entity_id,
            "is_volume_muted": False,
        },
        blocking=True,
    )
    assert len(mock_api.remote_unmute.mock_calls) == 1


async def test_browse_media(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    init_integration: MockConfigEntry,
    mock_jellyfin: MagicMock,
    mock_api: MagicMock,
) -> None:
    """Test Jellyfin browse media."""
    client = await hass_ws_client()

    # browse root folder
    await client.send_json(
        {
            "id": 1,
            "type": "media_player/browse_media",
            "entity_id": "media_player.jellyfin_device",
        }
    )
    response = await client.receive_json()
    assert response["success"]
    expected_child_item = {
        "title": "COLLECTION FOLDER",
        "media_class": MediaClass.DIRECTORY.value,
        "media_content_type": "collection",
        "media_content_id": "COLLECTION-FOLDER-UUID",
        "can_play": False,
        "can_expand": True,
        "thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
        "children_media_class": None,
    }

    assert response["result"]["media_content_id"] == ""
    assert response["result"]["media_content_type"] == "root"
    assert response["result"]["title"] == "Jellyfin"
    assert response["result"]["children"][0] == expected_child_item

    # browse collection folder
    await client.send_json(
        {
            "id": 2,
            "type": "media_player/browse_media",
            "entity_id": "media_player.jellyfin_device",
            "media_content_type": "collection",
            "media_content_id": "COLLECTION-FOLDER-UUID",
        }
    )

    response = await client.receive_json()
    expected_child_item = {
        "title": "EPISODE",
        "media_class": MediaClass.EPISODE.value,
        "media_content_type": MediaType.EPISODE.value,
        "media_content_id": "EPISODE-UUID",
        "can_play": True,
        "can_expand": False,
        "thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
        "children_media_class": None,
    }

    assert response["success"]
    assert response["result"]["media_content_id"] == "COLLECTION-FOLDER-UUID"
    assert response["result"]["title"] == "FOLDER"
    assert response["result"]["children"][0] == expected_child_item

    # browse for collection without children
    mock_api.user_items.side_effect = None
    mock_api.user_items.return_value = {}

    await client.send_json(
        {
            "id": 3,
            "type": "media_player/browse_media",
            "entity_id": "media_player.jellyfin_device",
            "media_content_type": "collection",
            "media_content_id": "COLLECTION-FOLDER-UUID",
        }
    )

    response = await client.receive_json()
    assert response["success"] is False
    assert response["error"]
    assert (
        response["error"]["message"]
        == "Media not found: collection / COLLECTION-FOLDER-UUID"
    )

    # browse for non-existent item
    mock_api.get_item.side_effect = None
    mock_api.get_item.return_value = {}

    await client.send_json(
        {
            "id": 4,
            "type": "media_player/browse_media",
            "entity_id": "media_player.jellyfin_device",
            "media_content_type": "collection",
            "media_content_id": "COLLECTION-UUID-404",
        }
    )

    response = await client.receive_json()
    assert response["success"] is False
    assert response["error"]
    assert (
        response["error"]["message"]
        == "Media not found: collection / COLLECTION-UUID-404"
    )


async def test_new_client_connected(
    hass: HomeAssistant,
    init_integration: MockConfigEntry,
    mock_jellyfin: MagicMock,
    mock_api: MagicMock,
) -> None:
    """Test Jellyfin media player reacts to new clients connecting."""
    mock_api.sessions.return_value = await async_load_json_fixture(
        hass,
        "sessions-new-client.json",
    )

    assert len(mock_api.sessions.mock_calls) == 1
    async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
    await hass.async_block_till_done()
    assert len(mock_api.sessions.mock_calls) == 2

    state = hass.states.get("media_player.jellyfin_device_five")
    assert state
