"""Support for Epson projector."""
from __future__ import annotations

import logging

from epson_projector import Projector, ProjectorUnavailableError
from epson_projector.const import (
    BACK,
    BUSY,
    CMODE,
    CMODE_LIST,
    CMODE_LIST_SET,
    DEFAULT_SOURCES,
    EPSON_CODES,
    FAST,
    INV_SOURCES,
    MUTE,
    PAUSE,
    PLAY,
    POWER,
    SOURCE,
    SOURCE_LIST,
    TURN_OFF,
    TURN_ON,
    VOL_DOWN,
    VOL_UP,
    VOLUME,
)
import voluptuous as vol

from homeassistant.components.media_player import (
    MediaPlayerEntity,
    MediaPlayerEntityFeature,
    MediaPlayerState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry

from .const import ATTR_CMODE, DOMAIN, SERVICE_SELECT_CMODE

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the Epson projector from a config entry."""
    projector: Projector = hass.data[DOMAIN][config_entry.entry_id]
    projector_entity = EpsonProjectorMediaPlayer(
        projector=projector,
        name=config_entry.title,
        unique_id=config_entry.unique_id,
        entry=config_entry,
    )
    async_add_entities([projector_entity], True)
    platform = entity_platform.async_get_current_platform()
    platform.async_register_entity_service(
        SERVICE_SELECT_CMODE,
        {vol.Required(ATTR_CMODE): vol.All(cv.string, vol.Any(*CMODE_LIST_SET))},
        SERVICE_SELECT_CMODE,
    )


class EpsonProjectorMediaPlayer(MediaPlayerEntity):
    """Representation of Epson Projector Device."""

    _attr_supported_features = (
        MediaPlayerEntityFeature.TURN_ON
        | MediaPlayerEntityFeature.TURN_OFF
        | MediaPlayerEntityFeature.SELECT_SOURCE
        | MediaPlayerEntityFeature.VOLUME_MUTE
        | MediaPlayerEntityFeature.VOLUME_STEP
        | MediaPlayerEntityFeature.NEXT_TRACK
        | MediaPlayerEntityFeature.PREVIOUS_TRACK
    )

    def __init__(
        self, projector: Projector, name: str, unique_id: str | None, entry: ConfigEntry
    ) -> None:
        """Initialize entity to control Epson projector."""
        self._projector = projector
        self._entry = entry
        self._attr_name = name
        self._attr_available = False
        self._cmode = None
        self._attr_source_list = list(DEFAULT_SOURCES.values())
        self._attr_unique_id = unique_id
        if unique_id:
            self._attr_device_info = DeviceInfo(
                identifiers={(DOMAIN, unique_id)},
                manufacturer="Epson",
                model="Epson",
                name="Epson projector",
                via_device=(DOMAIN, unique_id),
            )

    async def set_unique_id(self) -> bool:
        """Set unique id for projector config entry."""
        _LOGGER.debug("Setting unique_id for projector")
        if self.unique_id:
            return False
        if uid := await self._projector.get_serial_number():
            self.hass.config_entries.async_update_entry(self._entry, unique_id=uid)
            registry = async_get_entity_registry(self.hass)
            old_entity_id = registry.async_get_entity_id(
                "media_player", DOMAIN, self._entry.entry_id
            )
            if old_entity_id is not None:
                registry.async_update_entity(old_entity_id, new_unique_id=uid)
            self.hass.async_create_task(
                self.hass.config_entries.async_reload(self._entry.entry_id)
            )
            return True
        return False

    async def async_update(self) -> None:
        """Update state of device."""
        try:
            power_state = await self._projector.get_power()
        except ProjectorUnavailableError as ex:
            _LOGGER.debug("Projector is unavailable: %s", ex)
            self._attr_available = False
            return
        if not power_state:
            self._attr_available = False
            return
        _LOGGER.debug("Projector status: %s", power_state)
        self._attr_available = True
        if power_state == EPSON_CODES[POWER]:
            self._attr_state = MediaPlayerState.ON
            if await self.set_unique_id():
                return
            self._attr_source_list = list(DEFAULT_SOURCES.values())
            cmode = await self._projector.get_property(CMODE)
            self._cmode = CMODE_LIST.get(cmode, self._cmode)
            source = await self._projector.get_property(SOURCE)
            self._attr_source = SOURCE_LIST.get(source, self._attr_source)
            if volume := await self._projector.get_property(VOLUME):
                try:
                    self._attr_volume_level = float(volume)
                except ValueError:
                    self._attr_volume_level = None
        elif power_state == BUSY:
            self._attr_state = MediaPlayerState.ON
        else:
            self._attr_state = MediaPlayerState.OFF

    async def async_turn_on(self) -> None:
        """Turn on epson."""
        if self.state == MediaPlayerState.OFF:
            await self._projector.send_command(TURN_ON)
            self._attr_state = MediaPlayerState.ON

    async def async_turn_off(self) -> None:
        """Turn off epson."""
        if self.state == MediaPlayerState.ON:
            await self._projector.send_command(TURN_OFF)
            self._attr_state = MediaPlayerState.OFF

    async def select_cmode(self, cmode: str) -> None:
        """Set color mode in Epson."""
        await self._projector.send_command(CMODE_LIST_SET[cmode])

    async def async_select_source(self, source: str) -> None:
        """Select input source."""
        selected_source = INV_SOURCES[source]
        await self._projector.send_command(selected_source)

    async def async_mute_volume(self, mute: bool) -> None:
        """Mute (true) or unmute (false) sound."""
        await self._projector.send_command(MUTE)

    async def async_volume_up(self) -> None:
        """Increase volume."""
        await self._projector.send_command(VOL_UP)

    async def async_volume_down(self) -> None:
        """Decrease volume."""
        await self._projector.send_command(VOL_DOWN)

    async def async_media_play(self) -> None:
        """Play media via Epson."""
        await self._projector.send_command(PLAY)

    async def async_media_pause(self) -> None:
        """Pause media via Epson."""
        await self._projector.send_command(PAUSE)

    async def async_media_next_track(self) -> None:
        """Skip to next."""
        await self._projector.send_command(FAST)

    async def async_media_previous_track(self) -> None:
        """Skip to previous."""
        await self._projector.send_command(BACK)

    @property
    def extra_state_attributes(self) -> dict[str, str]:
        """Return device specific state attributes."""
        if self._cmode is None:
            return {}
        return {ATTR_CMODE: self._cmode}