"""Arcam media player."""

import logging
from typing import Any, override

from arcam.fmj import SourceCodes

from homeassistant.components.media_player import (
    BrowseError,
    BrowseMedia,
    MediaClass,
    MediaPlayerEntity,
    MediaPlayerEntityFeature,
    MediaPlayerState,
    MediaType,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import DOMAIN, EVENT_TURN_ON
from .coordinator import ArcamFmjConfigEntry, ArcamFmjCoordinator
from .entity import ArcamFmjEntity, convert_exception

_LOGGER = logging.getLogger(__name__)

# arcam-fmj serializes commands on a single TCP writer at the library
# layer; serialize at HA's layer to match the device's contract.
PARALLEL_UPDATES = 1


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ArcamFmjConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the configuration entry."""
    coordinators = config_entry.runtime_data.coordinators

    async_add_entities(
        [ArcamFmj(coordinators[zone]) for zone in (1, 2)],
    )


class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
    """Representation of a media device."""

    def __init__(self, coordinator: ArcamFmjCoordinator) -> None:
        """Initialize device."""
        super().__init__(coordinator)
        self._state = coordinator.state
        self._attr_supported_features = (
            MediaPlayerEntityFeature.SELECT_SOURCE
            | MediaPlayerEntityFeature.PLAY_MEDIA
            | MediaPlayerEntityFeature.BROWSE_MEDIA
            | MediaPlayerEntityFeature.VOLUME_SET
            | MediaPlayerEntityFeature.VOLUME_MUTE
            | MediaPlayerEntityFeature.VOLUME_STEP
            | MediaPlayerEntityFeature.TURN_OFF
            | MediaPlayerEntityFeature.TURN_ON
        )
        if self._state.zn == 1:
            self._attr_supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE

    @property
    @override
    def state(self) -> MediaPlayerState | None:
        """Return the state of the device.

        ``None`` is returned (surfaced as ``unknown``) when the device has
        not yet reported a power state; this is distinct from a real
        powered-off state and must not be collapsed to ``OFF``.
        """
        power = self._state.get_power()
        if power is None:
            return None
        return MediaPlayerState.ON if power else MediaPlayerState.OFF

    @convert_exception
    @override
    async def async_mute_volume(self, mute: bool) -> None:
        """Send mute command."""
        await self._state.set_mute(mute)
        self.async_write_ha_state()

    @convert_exception
    @override
    async def async_select_source(self, source: str) -> None:
        """Select a specific source."""
        try:
            value = SourceCodes[source]
        except KeyError as exception:
            raise ServiceValidationError(
                translation_domain=DOMAIN,
                translation_key="unsupported_source",
                translation_placeholders={"source": source},
            ) from exception

        await self._state.set_source(value)
        self.async_write_ha_state()

    @convert_exception
    @override
    async def async_select_sound_mode(self, sound_mode: str) -> None:
        """Select a specific source."""
        try:
            await self._state.set_decode_mode(sound_mode)
        except (KeyError, ValueError) as exception:
            raise ServiceValidationError(
                translation_domain=DOMAIN,
                translation_key="unsupported_sound_mode",
                translation_placeholders={"sound_mode": sound_mode},
            ) from exception

        self.async_write_ha_state()

    @convert_exception
    @override
    async def async_set_volume_level(self, volume: float) -> None:
        """Set volume level, range 0..1."""
        await self._state.set_volume(round(volume * 99.0))
        self.async_write_ha_state()

    @convert_exception
    @override
    async def async_volume_up(self) -> None:
        """Turn volume up for media player."""
        await self._state.inc_volume()
        self.async_write_ha_state()

    @convert_exception
    @override
    async def async_volume_down(self) -> None:
        """Turn volume up for media player."""
        await self._state.dec_volume()
        self.async_write_ha_state()

    @convert_exception
    @override
    async def async_turn_on(self) -> None:
        """Turn the media player on."""
        if self._state.get_power() is not None:
            _LOGGER.debug("Turning on device using connection")
            await self._state.set_power(True)
        else:
            _LOGGER.debug("Firing event to turn on device")
            self.hass.bus.async_fire(EVENT_TURN_ON, {ATTR_ENTITY_ID: self.entity_id})

    @convert_exception
    @override
    async def async_turn_off(self) -> None:
        """Turn the media player off."""
        await self._state.set_power(False)

    @override
    async def async_browse_media(
        self,
        media_content_type: MediaType | str | None = None,
        media_content_id: str | None = None,
    ) -> BrowseMedia:
        """Implement the websocket media browsing helper."""
        if media_content_id not in (None, "root"):
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}"
            )

        presets = self._state.get_preset_details()

        radio = [
            BrowseMedia(
                title=preset.name,
                media_class=MediaClass.MUSIC,
                media_content_id=f"preset:{preset.index}",
                media_content_type=MediaType.MUSIC,
                can_play=True,
                can_expand=False,
            )
            for preset in presets.values()
        ]

        return BrowseMedia(
            title=self.coordinator.device_name,
            media_class=MediaClass.DIRECTORY,
            media_content_id="root",
            media_content_type="library",
            can_play=False,
            can_expand=True,
            children=radio,
        )

    @convert_exception
    @override
    async def async_play_media(
        self, media_type: MediaType | str, media_id: str, **kwargs: Any
    ) -> None:
        """Play media."""

        if media_id.startswith("preset:"):
            preset = int(media_id[7:])
            await self._state.set_tuner_preset(preset)
        else:
            raise ServiceValidationError(
                translation_domain=DOMAIN,
                translation_key="unsupported_media",
                translation_placeholders={"media": media_id},
            )

    @property
    @override
    def source(self) -> str | None:
        """Return the current input source."""
        if (value := self._state.get_source()) is None:
            return None
        return value.name

    @property
    @override
    def source_list(self) -> list[str]:
        """List of available input sources."""
        return [x.name for x in self._state.get_source_list()]

    @property
    @override
    def sound_mode(self) -> str | None:
        """Name of the current sound mode."""
        if (value := self._state.get_decode_mode()) is None:
            return None
        return value.name

    @property
    @override
    def sound_mode_list(self) -> list[str] | None:
        """List of available sound modes."""
        if (values := self._state.get_decode_modes()) is None:
            return None
        return [x.name for x in values]

    @property
    @override
    def is_volume_muted(self) -> bool | None:
        """Boolean if volume is currently muted."""
        if (value := self._state.get_mute()) is None:
            return None
        return value

    @property
    @override
    def volume_level(self) -> float | None:
        """Volume level of device."""
        if (value := self._state.get_volume()) is None:
            return None
        return value / 99.0

    @property
    @override
    def media_content_type(self) -> MediaType | None:
        """Content type of current playing media."""
        source = self._state.get_source()
        if source in (SourceCodes.DAB, SourceCodes.FM):
            value = MediaType.MUSIC
        else:
            value = None
        return value

    @property
    @override
    def media_content_id(self) -> str | None:
        """Content type of current playing media."""
        source = self._state.get_source()
        if source in (SourceCodes.DAB, SourceCodes.FM):
            if preset := self._state.get_tuner_preset():
                value = f"preset:{preset}"
            else:
                value = None
        else:
            value = None

        return value

    @property
    @override
    def media_channel(self) -> str | None:
        """Channel currently playing."""
        source = self._state.get_source()
        if source is SourceCodes.DAB:
            value = self._state.get_dab_station()
        elif source is SourceCodes.FM:
            value = self._state.get_rds_information()
        else:
            value = None
        return value

    @property
    @override
    def media_artist(self) -> str | None:
        """Artist of current playing media, music track only."""
        if self._state.get_source() is SourceCodes.DAB:
            value = self._state.get_dls_pdt()
        else:
            value = None
        return value

    @property
    @override
    def media_title(self) -> str | None:
        """Title of current playing media."""
        if (source := self._state.get_source()) is None:
            return None

        if channel := self.media_channel:
            value = f"{source.name} - {channel}"
        else:
            value = source.name
        return value
