"""Media player support for Android TV Remote."""
from __future__ import annotations

import asyncio
from typing import Any

from androidtvremote2 import AndroidTVRemote, ConnectionClosed

from homeassistant.components.media_player import (
    MediaPlayerDeviceClass,
    MediaPlayerEntity,
    MediaPlayerEntityFeature,
    MediaPlayerState,
    MediaType,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .entity import AndroidTVRemoteBaseEntity

PARALLEL_UPDATES = 0


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the Android TV media player entity based on a config entry."""
    api: AndroidTVRemote = hass.data[DOMAIN][config_entry.entry_id]
    async_add_entities([AndroidTVRemoteMediaPlayerEntity(api, config_entry)])


class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEntity):
    """Android TV Remote Media Player Entity."""

    _attr_assumed_state = True
    _attr_device_class = MediaPlayerDeviceClass.TV
    _attr_supported_features = (
        MediaPlayerEntityFeature.PAUSE
        | MediaPlayerEntityFeature.VOLUME_STEP
        | MediaPlayerEntityFeature.VOLUME_MUTE
        | MediaPlayerEntityFeature.PREVIOUS_TRACK
        | MediaPlayerEntityFeature.NEXT_TRACK
        | MediaPlayerEntityFeature.TURN_ON
        | MediaPlayerEntityFeature.TURN_OFF
        | MediaPlayerEntityFeature.PLAY
        | MediaPlayerEntityFeature.STOP
        | MediaPlayerEntityFeature.PLAY_MEDIA
    )

    def __init__(self, api: AndroidTVRemote, config_entry: ConfigEntry) -> None:
        """Initialize the entity."""
        super().__init__(api, config_entry)

        # This task is needed to create a job that sends a key press
        # sequence that can be canceled if concurrency occurs
        self._channel_set_task: asyncio.Task | None = None

    def _update_current_app(self, current_app: str) -> None:
        """Update current app info."""
        self._attr_app_id = current_app
        self._attr_app_name = current_app

    def _update_volume_info(self, volume_info: dict[str, str | bool]) -> None:
        """Update volume info."""
        if volume_info.get("max"):
            self._attr_volume_level = int(volume_info["level"]) / int(
                volume_info["max"]
            )
            self._attr_is_volume_muted = bool(volume_info["muted"])
        else:
            self._attr_volume_level = None
            self._attr_is_volume_muted = None

    @callback
    def _current_app_updated(self, current_app: str) -> None:
        """Update the state when the current app changes."""
        self._update_current_app(current_app)
        self.async_write_ha_state()

    @callback
    def _volume_info_updated(self, volume_info: dict[str, str | bool]) -> None:
        """Update the state when the volume info changes."""
        self._update_volume_info(volume_info)
        self.async_write_ha_state()

    async def async_added_to_hass(self) -> None:
        """Register callbacks."""
        await super().async_added_to_hass()

        self._update_current_app(self._api.current_app)
        self._update_volume_info(self._api.volume_info)

        self._api.add_current_app_updated_callback(self._current_app_updated)
        self._api.add_volume_info_updated_callback(self._volume_info_updated)

    async def async_will_remove_from_hass(self) -> None:
        """Remove callbacks."""
        await super().async_will_remove_from_hass()

        self._api.remove_current_app_updated_callback(self._current_app_updated)
        self._api.remove_volume_info_updated_callback(self._volume_info_updated)

    @property
    def state(self) -> MediaPlayerState:
        """Return the state of the device."""
        if self._attr_is_on:
            return MediaPlayerState.ON
        return MediaPlayerState.OFF

    async def async_turn_on(self) -> None:
        """Turn the Android TV on."""
        if not self._attr_is_on:
            self._send_key_command("POWER")

    async def async_turn_off(self) -> None:
        """Turn the Android TV off."""
        if self._attr_is_on:
            self._send_key_command("POWER")

    async def async_volume_up(self) -> None:
        """Turn volume up for media player."""
        self._send_key_command("VOLUME_UP")

    async def async_volume_down(self) -> None:
        """Turn volume down for media player."""
        self._send_key_command("VOLUME_DOWN")

    async def async_mute_volume(self, mute: bool) -> None:
        """Mute the volume."""
        if mute != self.is_volume_muted:
            self._send_key_command("VOLUME_MUTE")

    async def async_media_play(self) -> None:
        """Send play command."""
        self._send_key_command("MEDIA_PLAY")

    async def async_media_pause(self) -> None:
        """Send pause command."""
        self._send_key_command("MEDIA_PAUSE")

    async def async_media_play_pause(self) -> None:
        """Send play/pause command."""
        self._send_key_command("MEDIA_PLAY_PAUSE")

    async def async_media_stop(self) -> None:
        """Send stop command."""
        self._send_key_command("MEDIA_STOP")

    async def async_media_previous_track(self) -> None:
        """Send previous track command."""
        self._send_key_command("MEDIA_PREVIOUS")

    async def async_media_next_track(self) -> None:
        """Send next track command."""
        self._send_key_command("MEDIA_NEXT")

    async def async_play_media(
        self, media_type: MediaType | str, media_id: str, **kwargs: Any
    ) -> None:
        """Play a piece of media."""
        if media_type == MediaType.CHANNEL:
            if not media_id.isnumeric():
                raise ValueError(f"Channel must be numeric: {media_id}")
            if self._channel_set_task:
                self._channel_set_task.cancel()
            self._channel_set_task = asyncio.create_task(
                self._send_key_commands(list(media_id))
            )
            await self._channel_set_task
            return

        if media_type == MediaType.URL:
            self._send_launch_app_command(media_id)
            return

        raise ValueError(f"Invalid media type: {media_type}")

    async def _send_key_commands(
        self, key_codes: list[str], delay_secs: float = 0.1
    ) -> None:
        """Send a key press sequence to Android TV.

        The delay is necessary because device may ignore
        some commands if we send the sequence without delay.
        """
        try:
            for key_code in key_codes:
                self._api.send_key_command(key_code)
                await asyncio.sleep(delay_secs)
        except ConnectionClosed as exc:
            raise HomeAssistantError(
                "Connection to Android TV device is closed"
            ) from exc