"""Support for Frontier Silicon Devices (Medion, Hama, Auna,...)."""
from __future__ import annotations

import logging
from typing import Any

from afsapi import (
    AFSAPI,
    ConnectionError as FSConnectionError,
    NotImplementedException as FSNotImplementedException,
    PlayState,
)
import voluptuous as vol

from homeassistant.components.media_player import (
    PLATFORM_SCHEMA,
    BrowseError,
    BrowseMedia,
    MediaPlayerEntity,
    MediaPlayerEntityFeature,
    MediaPlayerState,
    MediaType,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .browse_media import browse_node, browse_top_level
from .const import DEFAULT_PIN, DEFAULT_PORT, DOMAIN, MEDIA_CONTENT_ID_PRESET

_LOGGER = logging.getLogger(__name__)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_HOST): cv.string,
        vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
        vol.Optional(CONF_PASSWORD, default=DEFAULT_PIN): cv.string,
        vol.Optional(CONF_NAME): cv.string,
    }
)


async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up the Frontier Silicon platform."""
    if discovery_info is not None:
        webfsapi_url = await AFSAPI.get_webfsapi_endpoint(
            discovery_info["ssdp_description"]
        )
        afsapi = AFSAPI(webfsapi_url, DEFAULT_PIN)

        name = await afsapi.get_friendly_name()
        async_add_entities(
            [AFSAPIDevice(name, afsapi)],
            True,
        )
        return

    host = config.get(CONF_HOST)
    port = config.get(CONF_PORT)
    password = config.get(CONF_PASSWORD)
    name = config.get(CONF_NAME)

    try:
        webfsapi_url = await AFSAPI.get_webfsapi_endpoint(
            f"http://{host}:{port}/device"
        )
    except FSConnectionError:
        _LOGGER.error(
            "Could not add the FSAPI device at %s:%s -> %s", host, port, password
        )
        return
    afsapi = AFSAPI(webfsapi_url, password)
    async_add_entities([AFSAPIDevice(name, afsapi)], True)


class AFSAPIDevice(MediaPlayerEntity):
    """Representation of a Frontier Silicon device on the network."""

    _attr_media_content_type: str = MediaType.CHANNEL

    _attr_supported_features = (
        MediaPlayerEntityFeature.PAUSE
        | MediaPlayerEntityFeature.VOLUME_SET
        | MediaPlayerEntityFeature.VOLUME_MUTE
        | MediaPlayerEntityFeature.VOLUME_STEP
        | MediaPlayerEntityFeature.PREVIOUS_TRACK
        | MediaPlayerEntityFeature.NEXT_TRACK
        | MediaPlayerEntityFeature.SEEK
        | MediaPlayerEntityFeature.PLAY_MEDIA
        | MediaPlayerEntityFeature.PLAY
        | MediaPlayerEntityFeature.STOP
        | MediaPlayerEntityFeature.TURN_ON
        | MediaPlayerEntityFeature.TURN_OFF
        | MediaPlayerEntityFeature.SELECT_SOURCE
        | MediaPlayerEntityFeature.SELECT_SOUND_MODE
        | MediaPlayerEntityFeature.BROWSE_MEDIA
    )

    def __init__(self, name: str | None, afsapi: AFSAPI) -> None:
        """Initialize the Frontier Silicon API device."""
        self.fs_device = afsapi

        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, afsapi.webfsapi_endpoint)},
            name=name,
        )
        self._attr_name = name

        self._max_volume: int | None = None

        self.__modes_by_label: dict[str, str] | None = None
        self.__sound_modes_by_label: dict[str, str] | None = None

        self._supports_sound_mode: bool = True

    async def async_update(self) -> None:
        """Get the latest date and update device state."""
        afsapi = self.fs_device
        try:
            if await afsapi.get_power():
                status = await afsapi.get_play_status()
                self._attr_state = {
                    PlayState.PLAYING: MediaPlayerState.PLAYING,
                    PlayState.PAUSED: MediaPlayerState.PAUSED,
                    PlayState.STOPPED: MediaPlayerState.IDLE,
                    PlayState.LOADING: MediaPlayerState.BUFFERING,
                    None: MediaPlayerState.IDLE,
                }.get(status)
            else:
                self._attr_state = MediaPlayerState.OFF
        except FSConnectionError:
            if self._attr_available:
                _LOGGER.warning(
                    "Could not connect to %s. Did it go offline?",
                    self.name or afsapi.webfsapi_endpoint,
                )
                self._attr_available = False
                return

        if not self._attr_available:
            _LOGGER.info(
                "Reconnected to %s",
                self.name or afsapi.webfsapi_endpoint,
            )

            self._attr_available = True
        if not self._attr_name:
            self._attr_name = await afsapi.get_friendly_name()

        if not self._attr_source_list:
            self.__modes_by_label = {
                mode.label: mode.key for mode in await afsapi.get_modes()
            }
            self._attr_source_list = list(self.__modes_by_label)

        if not self._attr_sound_mode_list and self._supports_sound_mode:
            try:
                equalisers = await afsapi.get_equalisers()
            except FSNotImplementedException:
                self._supports_sound_mode = False
                # Remove SELECT_SOUND_MODE from the advertised supported features
                self._attr_supported_features ^= (
                    MediaPlayerEntityFeature.SELECT_SOUND_MODE
                )
            else:
                self.__sound_modes_by_label = {
                    sound_mode.label: sound_mode.key for sound_mode in equalisers
                }
                self._attr_sound_mode_list = list(self.__sound_modes_by_label)

        # The API seems to include 'zero' in the number of steps (e.g. if the range is
        # 0-40 then get_volume_steps returns 41) subtract one to get the max volume.
        # If call to get_volume fails set to 0 and try again next time.
        if not self._max_volume:
            self._max_volume = int(await afsapi.get_volume_steps() or 1) - 1

        if self._attr_state != MediaPlayerState.OFF:
            info_name = await afsapi.get_play_name()
            info_text = await afsapi.get_play_text()

            self._attr_media_title = " - ".join(filter(None, [info_name, info_text]))
            self._attr_media_artist = await afsapi.get_play_artist()
            self._attr_media_album_name = await afsapi.get_play_album()

            radio_mode = await afsapi.get_mode()
            self._attr_source = radio_mode.label if radio_mode is not None else None

            self._attr_is_volume_muted = await afsapi.get_mute()
            self._attr_media_image_url = await afsapi.get_play_graphic()

            if self._supports_sound_mode:
                try:
                    eq_preset = await afsapi.get_eq_preset()
                except FSNotImplementedException:
                    self._supports_sound_mode = False
                    # Remove SELECT_SOUND_MODE from the advertised supported features
                    self._attr_supported_features ^= (
                        MediaPlayerEntityFeature.SELECT_SOUND_MODE
                    )
                else:
                    self._attr_sound_mode = (
                        eq_preset.label if eq_preset is not None else None
                    )

            volume = await self.fs_device.get_volume()

            # Prevent division by zero if max_volume not known yet
            self._attr_volume_level = float(volume or 0) / (self._max_volume or 1)
        else:
            self._attr_media_title = None
            self._attr_media_artist = None
            self._attr_media_album_name = None

            self._attr_source = None

            self._attr_is_volume_muted = None
            self._attr_media_image_url = None
            self._attr_sound_mode = None

            self._attr_volume_level = None

    # Management actions
    # power control
    async def async_turn_on(self) -> None:
        """Turn on the device."""
        await self.fs_device.set_power(True)

    async def async_turn_off(self) -> None:
        """Turn off the device."""
        await self.fs_device.set_power(False)

    async def async_media_play(self) -> None:
        """Send play command."""
        await self.fs_device.play()

    async def async_media_pause(self) -> None:
        """Send pause command."""
        await self.fs_device.pause()

    async def async_media_play_pause(self) -> None:
        """Send play/pause command."""
        if self._attr_state == MediaPlayerState.PLAYING:
            await self.fs_device.pause()
        else:
            await self.fs_device.play()

    async def async_media_stop(self) -> None:
        """Send play/pause command."""
        await self.fs_device.pause()

    async def async_media_previous_track(self) -> None:
        """Send previous track command (results in rewind)."""
        await self.fs_device.rewind()

    async def async_media_next_track(self) -> None:
        """Send next track command (results in fast-forward)."""
        await self.fs_device.forward()

    async def async_mute_volume(self, mute: bool) -> None:
        """Send mute command."""
        await self.fs_device.set_mute(mute)

    # volume
    async def async_volume_up(self) -> None:
        """Send volume up command."""
        volume = await self.fs_device.get_volume()
        volume = int(volume or 0) + 1
        await self.fs_device.set_volume(min(volume, self._max_volume))

    async def async_volume_down(self) -> None:
        """Send volume down command."""
        volume = await self.fs_device.get_volume()
        volume = int(volume or 0) - 1
        await self.fs_device.set_volume(max(volume, 0))

    async def async_set_volume_level(self, volume: float) -> None:
        """Set volume command."""
        if self._max_volume:  # Can't do anything sensible if not set
            volume = int(volume * self._max_volume)
            await self.fs_device.set_volume(volume)

    async def async_select_source(self, source: str) -> None:
        """Select input source."""
        await self.fs_device.set_power(True)
        if (
            self.__modes_by_label
            and (mode := self.__modes_by_label.get(source)) is not None
        ):
            await self.fs_device.set_mode(mode)

    async def async_select_sound_mode(self, sound_mode: str) -> None:
        """Select EQ Preset."""
        if (
            self.__sound_modes_by_label
            and (mode := self.__sound_modes_by_label.get(sound_mode)) is not None
        ):
            await self.fs_device.set_eq_preset(mode)

    async def async_browse_media(
        self, media_content_type: str | None = None, media_content_id: str | None = None
    ) -> BrowseMedia:
        """Browse media library and preset stations."""
        if not media_content_id:
            return await browse_top_level(self._attr_source, self.fs_device)

        return await browse_node(self.fs_device, media_content_type, media_content_id)

    async def async_play_media(
        self, media_type: MediaType | str, media_id: str, **kwargs: Any
    ) -> None:
        """Play selected media or channel."""
        if media_type != MediaType.CHANNEL:
            _LOGGER.error(
                "Got %s, but frontier_silicon only supports playing channels",
                media_type,
            )
            return

        player_mode, media_type, *keys = media_id.split("/")

        await self.async_select_source(player_mode)  # this also powers on the device

        if media_type == MEDIA_CONTENT_ID_PRESET:
            if len(keys) != 1:
                raise BrowseError("Presets can only have 1 level")

            # Keys of presets are 0-based, while the list shown on the device starts from 1
            preset = int(keys[0]) - 1

            result = await self.fs_device.select_preset(preset)
        else:
            result = await self.fs_device.nav_select_item_via_path(keys)

        await self.async_update()
        self._attr_media_content_id = media_id
        return result
