"""Support for Epson projector."""

import logging
from typing import override

from epson_projector import Projector, ProjectorUnavailableError
from epson_projector.const import (
    BACK,
    BUSY_CODES,
    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,
)

from homeassistant.components.media_player import (
    MediaPlayerDeviceClass,
    MediaPlayerEntity,
    MediaPlayerEntityFeature,
    MediaPlayerState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import EpsonConfigEntry
from .const import ATTR_CMODE, DOMAIN

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: EpsonConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the Epson projector from a config entry."""
    projector_entity = EpsonProjectorMediaPlayer(
        projector=config_entry.runtime_data,
        unique_id=config_entry.unique_id or config_entry.entry_id,
        entry=config_entry,
    )
    async_add_entities([projector_entity], True)


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

    _attr_has_entity_name = True
    _attr_name = None
    _attr_device_class = MediaPlayerDeviceClass.PROJECTOR

    _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, unique_id: str, entry: ConfigEntry
    ) -> None:
        """Initialize entity to control Epson projector."""
        self._projector = projector
        self._entry = entry
        self._attr_available = False
        self._cmode = None
        self._attr_source_list = list(DEFAULT_SOURCES.values())
        self._attr_unique_id = unique_id
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, unique_id)},
            manufacturer="Epson",
            model="Epson",
        )

    async def set_unique_id(self) -> bool:
        """Set unique id for projector config entry."""
        _LOGGER.debug("Setting unique_id for projector")
        if self._entry.unique_id:
            return False
        if uid := await self._projector.get_serial_number():
            self.hass.config_entries.async_update_entry(self._entry, unique_id=uid)
            ent_reg = er.async_get(self.hass)
            old_entity_id = ent_reg.async_get_entity_id(
                "media_player", DOMAIN, self._entry.entry_id
            )
            if old_entity_id is not None:
                ent_reg.async_update_entity(old_entity_id, new_unique_id=uid)
            dev_reg = dr.async_get(self.hass)
            device = dev_reg.async_get_device_by_identifier(
                (DOMAIN, self._entry.entry_id), self._entry.entry_id
            )
            if device is not None:
                dev_reg.async_update_device(device.id, new_identifiers={(DOMAIN, 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 in BUSY_CODES:
            self._attr_state = MediaPlayerState.ON
        else:
            self._attr_state = MediaPlayerState.OFF

    @override
    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

    @override
    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])

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

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

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

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

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

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

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

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

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