"""Tests for Plex server."""
import copy

from plexapi.exceptions import NotFound

from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.media_player.const import (
    ATTR_MEDIA_CONTENT_ID,
    ATTR_MEDIA_CONTENT_TYPE,
    MEDIA_TYPE_EPISODE,
    MEDIA_TYPE_MUSIC,
    MEDIA_TYPE_PLAYLIST,
    MEDIA_TYPE_VIDEO,
    SERVICE_PLAY_MEDIA,
)
from homeassistant.components.plex.const import (
    CONF_IGNORE_NEW_SHARED_USERS,
    CONF_IGNORE_PLEX_WEB_CLIENTS,
    CONF_MONITORED_USERS,
    DOMAIN,
    PLEX_UPDATE_PLATFORMS_SIGNAL,
    SERVERS,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import DEFAULT_DATA, DEFAULT_OPTIONS
from .mock_classes import (
    MockPlexAccount,
    MockPlexArtist,
    MockPlexLibrary,
    MockPlexLibrarySection,
    MockPlexMediaItem,
    MockPlexServer,
)

from tests.async_mock import patch
from tests.common import MockConfigEntry


async def test_new_users_available(hass):
    """Test setting up when new users available on Plex server."""

    MONITORED_USERS = {"Owner": {"enabled": True}}
    OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS)
    OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS

    entry = MockConfigEntry(
        domain=DOMAIN,
        data=DEFAULT_DATA,
        options=OPTIONS_WITH_USERS,
        unique_id=DEFAULT_DATA["server_id"],
    )

    mock_plex_server = MockPlexServer(config_entry=entry)

    with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
        "homeassistant.components.plex.PlexWebsocket.listen"
    ):
        entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(entry.entry_id)
        await hass.async_block_till_done()

    server_id = mock_plex_server.machineIdentifier

    with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
        async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
        await hass.async_block_till_done()

    monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users

    ignored_users = [x for x in monitored_users if not monitored_users[x]["enabled"]]
    assert len(monitored_users) == 1
    assert len(ignored_users) == 0

    sensor = hass.states.get("sensor.plex_plex_server_1")
    assert sensor.state == str(len(mock_plex_server.accounts))


async def test_new_ignored_users_available(hass, caplog):
    """Test setting up when new users available on Plex server but are ignored."""

    MONITORED_USERS = {"Owner": {"enabled": True}}
    OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS)
    OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS
    OPTIONS_WITH_USERS[MP_DOMAIN][CONF_IGNORE_NEW_SHARED_USERS] = True

    entry = MockConfigEntry(
        domain=DOMAIN,
        data=DEFAULT_DATA,
        options=OPTIONS_WITH_USERS,
        unique_id=DEFAULT_DATA["server_id"],
    )

    mock_plex_server = MockPlexServer(config_entry=entry)

    with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
        "homeassistant.components.plex.PlexWebsocket.listen"
    ):
        entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(entry.entry_id)
        await hass.async_block_till_done()

    server_id = mock_plex_server.machineIdentifier

    with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
        async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
        await hass.async_block_till_done()

    monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users

    ignored_users = [x for x in mock_plex_server.accounts if x not in monitored_users]
    assert len(monitored_users) == 1
    assert len(ignored_users) == 2
    for ignored_user in ignored_users:
        ignored_client = [
            x.players[0]
            for x in mock_plex_server.sessions()
            if x.usernames[0] == ignored_user
        ][0]
        assert (
            f"Ignoring {ignored_client.product} client owned by '{ignored_user}'"
            in caplog.text
        )

    sensor = hass.states.get("sensor.plex_plex_server_1")
    assert sensor.state == str(len(mock_plex_server.accounts))


# class TestClockedPlex(ClockedTestCase):
#     """Create clock-controlled tests.async_mock class."""

#     async def setUp(self):
#         """Initialize this test class."""
#         self.hass = await async_test_home_assistant(self.loop)

#     async def tearDown(self):
#         """Clean up the HomeAssistant instance."""
#         await self.hass.async_stop()

#     async def test_mark_sessions_idle(self):
#         """Test marking media_players as idle when sessions end."""
#         hass = self.hass

#         entry = MockConfigEntry(
#             domain=DOMAIN,
#             data=DEFAULT_DATA,
#             options=DEFAULT_OPTIONS,
#             unique_id=DEFAULT_DATA["server_id"],
#         )

#         mock_plex_server = MockPlexServer(config_entry=entry)

#         with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
#             "homeassistant.components.plex.PlexWebsocket.listen"
#         ):
#             entry.add_to_hass(hass)
#             assert await hass.config_entries.async_setup(entry.entry_id)
#             await hass.async_block_till_done()

#         server_id = mock_plex_server.machineIdentifier

#         async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
#         await hass.async_block_till_done()

#         sensor = hass.states.get("sensor.plex_plex_server_1")
#         assert sensor.state == str(len(mock_plex_server.accounts))

#         mock_plex_server.clear_clients()
#         mock_plex_server.clear_sessions()

#         await self.advance(DEBOUNCE_TIMEOUT)
#         async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
#         await hass.async_block_till_done()

#         sensor = hass.states.get("sensor.plex_plex_server_1")
#         assert sensor.state == "0"

#     async def test_debouncer(self):
#         """Test debouncer behavior."""
#         hass = self.hass

#         entry = MockConfigEntry(
#             domain=DOMAIN,
#             data=DEFAULT_DATA,
#             options=DEFAULT_OPTIONS,
#             unique_id=DEFAULT_DATA["server_id"],
#         )

#         mock_plex_server = MockPlexServer(config_entry=entry)

#         with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
#             "homeassistant.components.plex.PlexWebsocket.listen"
#         ):
#             entry.add_to_hass(hass)
#             assert await hass.config_entries.async_setup(entry.entry_id)
#             await hass.async_block_till_done()

#         server_id = mock_plex_server.machineIdentifier

#         with patch.object(mock_plex_server, "clients", return_value=[]), patch.object(
#             mock_plex_server, "sessions", return_value=[]
#         ) as mock_update:
#             # Called immediately
#             async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
#             await hass.async_block_till_done()
#             assert mock_update.call_count == 1

#             # Throttled
#             async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
#             await hass.async_block_till_done()
#             assert mock_update.call_count == 1

#             # Throttled
#             async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
#             await hass.async_block_till_done()
#             assert mock_update.call_count == 1

#             # Called from scheduler
#             await self.advance(DEBOUNCE_TIMEOUT)
#             await hass.async_block_till_done()
#             assert mock_update.call_count == 2

#             # Throttled
#             async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
#             await hass.async_block_till_done()
#             assert mock_update.call_count == 2

#             # Called from scheduler
#             await self.advance(DEBOUNCE_TIMEOUT)
#             await hass.async_block_till_done()
#             assert mock_update.call_count == 3


async def test_ignore_plex_web_client(hass):
    """Test option to ignore Plex Web clients."""

    OPTIONS = copy.deepcopy(DEFAULT_OPTIONS)
    OPTIONS[MP_DOMAIN][CONF_IGNORE_PLEX_WEB_CLIENTS] = True

    entry = MockConfigEntry(
        domain=DOMAIN,
        data=DEFAULT_DATA,
        options=OPTIONS,
        unique_id=DEFAULT_DATA["server_id"],
    )

    mock_plex_server = MockPlexServer(config_entry=entry)

    with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
        "homeassistant.components.plex.PlexWebsocket.listen"
    ):
        entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(entry.entry_id)
        await hass.async_block_till_done()

    server_id = mock_plex_server.machineIdentifier

    with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0)):
        async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
        await hass.async_block_till_done()

    sensor = hass.states.get("sensor.plex_plex_server_1")
    assert sensor.state == str(len(mock_plex_server.accounts))

    media_players = hass.states.async_entity_ids("media_player")

    assert len(media_players) == int(sensor.state) - 1


async def test_media_lookups(hass):
    """Test media lookups to Plex server."""

    entry = MockConfigEntry(
        domain=DOMAIN,
        data=DEFAULT_DATA,
        options=DEFAULT_OPTIONS,
        unique_id=DEFAULT_DATA["server_id"],
    )

    mock_plex_server = MockPlexServer(config_entry=entry)

    with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
        "homeassistant.components.plex.PlexWebsocket.listen"
    ):
        entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(entry.entry_id)
        await hass.async_block_till_done()

    server_id = mock_plex_server.machineIdentifier
    loaded_server = hass.data[DOMAIN][SERVERS][server_id]

    # Plex Key searches
    with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
        async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
        await hass.async_block_till_done()
    media_player_id = hass.states.async_entity_ids("media_player")[0]
    with patch("homeassistant.components.plex.PlexServer.create_playqueue"):
        assert await hass.services.async_call(
            MP_DOMAIN,
            SERVICE_PLAY_MEDIA,
            {
                ATTR_ENTITY_ID: media_player_id,
                ATTR_MEDIA_CONTENT_TYPE: DOMAIN,
                ATTR_MEDIA_CONTENT_ID: 123,
            },
            True,
        )
    with patch.object(MockPlexServer, "fetchItem", side_effect=NotFound):
        assert await hass.services.async_call(
            MP_DOMAIN,
            SERVICE_PLAY_MEDIA,
            {
                ATTR_ENTITY_ID: media_player_id,
                ATTR_MEDIA_CONTENT_TYPE: DOMAIN,
                ATTR_MEDIA_CONTENT_ID: 123,
            },
            True,
        )

    # TV show searches
    with patch.object(MockPlexLibrary, "section", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_EPISODE, library_name="Not a Library", show_name="A TV Show"
            )
            is None
        )
    with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="Not a TV Show"
            )
            is None
        )
    assert (
        loaded_server.lookup_media(
            MEDIA_TYPE_EPISODE, library_name="TV Shows", episode_name="An Episode"
        )
        is None
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="A TV Show"
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_EPISODE,
        library_name="TV Shows",
        show_name="A TV Show",
        season_number=2,
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_EPISODE,
        library_name="TV Shows",
        show_name="A TV Show",
        season_number=2,
        episode_number=3,
    )
    with patch.object(MockPlexMediaItem, "season", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_EPISODE,
                library_name="TV Shows",
                show_name="A TV Show",
                season_number=2,
            )
            is None
        )
    with patch.object(MockPlexMediaItem, "episode", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_EPISODE,
                library_name="TV Shows",
                show_name="A TV Show",
                season_number=2,
                episode_number=1,
            )
            is None
        )

    # Music searches
    assert (
        loaded_server.lookup_media(
            MEDIA_TYPE_MUSIC, library_name="Music", album_name="An Album"
        )
        is None
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_MUSIC, library_name="Music", artist_name="An Artist"
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_MUSIC,
        library_name="Music",
        artist_name="An Artist",
        track_name="A Track",
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_MUSIC,
        library_name="Music",
        artist_name="An Artist",
        album_name="An Album",
    )
    with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_MUSIC,
                library_name="Music",
                artist_name="Not an Artist",
                album_name="An Album",
            )
            is None
        )
    with patch.object(MockPlexArtist, "album", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_MUSIC,
                library_name="Music",
                artist_name="An Artist",
                album_name="Not an Album",
            )
            is None
        )
    with patch.object(MockPlexMediaItem, "track", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_MUSIC,
                library_name="Music",
                artist_name="An Artist",
                album_name="An Album",
                track_name="Not a Track",
            )
            is None
        )
    with patch.object(MockPlexArtist, "get", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_MUSIC,
                library_name="Music",
                artist_name="An Artist",
                track_name="Not a Track",
            )
            is None
        )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_MUSIC,
        library_name="Music",
        artist_name="An Artist",
        album_name="An Album",
        track_number=3,
    )
    assert (
        loaded_server.lookup_media(
            MEDIA_TYPE_MUSIC,
            library_name="Music",
            artist_name="An Artist",
            album_name="An Album",
            track_number=30,
        )
        is None
    )
    assert loaded_server.lookup_media(
        MEDIA_TYPE_MUSIC,
        library_name="Music",
        artist_name="An Artist",
        album_name="An Album",
        track_name="A Track",
    )

    # Playlist searches
    assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="A Playlist")
    assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST) is None
    with patch.object(MockPlexServer, "playlist", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_PLAYLIST, playlist_name="Not a Playlist"
            )
            is None
        )

    # Movie searches
    assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, video_name="A Movie") is None
    assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, library_name="Movies") is None
    assert loaded_server.lookup_media(
        MEDIA_TYPE_VIDEO, library_name="Movies", video_name="A Movie"
    )
    with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound):
        assert (
            loaded_server.lookup_media(
                MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Not a Movie"
            )
            is None
        )