"""Support for Roku selects."""
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass

from rokuecp import Roku
from rokuecp.models import Device as RokuDevice

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .coordinator import RokuDataUpdateCoordinator
from .entity import RokuEntity
from .helpers import format_channel_name, roku_exception_handler


@dataclass
class RokuSelectEntityDescriptionMixin:
    """Mixin for required keys."""

    options_fn: Callable[[RokuDevice], list[str]]
    value_fn: Callable[[RokuDevice], str | None]
    set_fn: Callable[[RokuDevice, Roku, str], Awaitable[None]]


def _get_application_name(device: RokuDevice) -> str | None:
    if device.app is None or device.app.name is None:
        return None

    if device.app.name == "Roku":
        return "Home"

    return device.app.name


def _get_applications(device: RokuDevice) -> list[str]:
    return ["Home"] + sorted(app.name for app in device.apps if app.name is not None)


def _get_channel_name(device: RokuDevice) -> str | None:
    if device.channel is None:
        return None

    return format_channel_name(device.channel.number, device.channel.name)


def _get_channels(device: RokuDevice) -> list[str]:
    return sorted(
        format_channel_name(channel.number, channel.name) for channel in device.channels
    )


async def _launch_application(device: RokuDevice, roku: Roku, value: str) -> None:
    if value == "Home":
        await roku.remote("home")

    appl = next(
        (app for app in device.apps if value == app.name),
        None,
    )

    if appl is not None and appl.app_id is not None:
        await roku.launch(appl.app_id)


async def _tune_channel(device: RokuDevice, roku: Roku, value: str) -> None:
    _channel = next(
        (
            channel
            for channel in device.channels
            if (
                channel.name is not None
                and value == format_channel_name(channel.number, channel.name)
            )
            or value == channel.number
        ),
        None,
    )

    if _channel is not None:
        await roku.tune(_channel.number)


@dataclass
class RokuSelectEntityDescription(
    SelectEntityDescription, RokuSelectEntityDescriptionMixin
):
    """Describes Roku select entity."""


ENTITIES: tuple[RokuSelectEntityDescription, ...] = (
    RokuSelectEntityDescription(
        key="application",
        name="Application",
        icon="mdi:application",
        set_fn=_launch_application,
        value_fn=_get_application_name,
        options_fn=_get_applications,
        entity_registry_enabled_default=False,
    ),
)

CHANNEL_ENTITY = RokuSelectEntityDescription(
    key="channel",
    name="Channel",
    icon="mdi:television",
    set_fn=_tune_channel,
    value_fn=_get_channel_name,
    options_fn=_get_channels,
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Roku select based on a config entry."""
    coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
    device: RokuDevice = coordinator.data
    unique_id = device.info.serial_number

    entities: list[RokuSelectEntity] = []

    for description in ENTITIES:
        entities.append(
            RokuSelectEntity(
                device_id=unique_id,
                coordinator=coordinator,
                description=description,
            )
        )

    if len(device.channels) > 0:
        entities.append(
            RokuSelectEntity(
                device_id=unique_id,
                coordinator=coordinator,
                description=CHANNEL_ENTITY,
            )
        )

    async_add_entities(entities)


class RokuSelectEntity(RokuEntity, SelectEntity):
    """Defines a Roku select entity."""

    entity_description: RokuSelectEntityDescription

    @property
    def current_option(self) -> str | None:
        """Return the current value."""
        return self.entity_description.value_fn(self.coordinator.data)

    @property
    def options(self) -> list[str]:
        """Return a set of selectable options."""
        return self.entity_description.options_fn(self.coordinator.data)

    @roku_exception_handler()
    async def async_select_option(self, option: str) -> None:
        """Set the option."""
        await self.entity_description.set_fn(
            self.coordinator.data,
            self.coordinator.roku,
            option,
        )
        await self.coordinator.async_request_refresh()
