"""
Alexa Devices notification service.

SPDX-License-Identifier: Apache-2.0

For more details about this platform, please refer to the documentation at
https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639
"""

import asyncio
import json
import logging

from alexapy.helpers import hide_email, hide_serial
from homeassistant.components.notify import (
    ATTR_DATA,
    ATTR_TARGET,
    ATTR_TITLE,
    ATTR_TITLE_DEFAULT,
    SERVICE_NOTIFY,
    BaseNotificationService,
)
from homeassistant.const import CONF_EMAIL
from homeassistant.helpers.group import expand_entity_ids
import voluptuous as vol

from .const import (
    CONF_QUEUE_DELAY,
    DATA_ALEXAMEDIA,
    DEFAULT_QUEUE_DELAY,
    DOMAIN,
    NOTIFY_URL,
)
from .helpers import retry_async

_LOGGER = logging.getLogger(__name__)


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_get_service(hass, config, discovery_info=None):
    # pylint: disable=unused-argument
    """Get the demo notification service."""
    result = False
    for account, account_dict in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
        for key, _ in account_dict["devices"]["media_player"].items():
            if key not in account_dict["entities"]["media_player"]:
                _LOGGER.debug(
                    "%s: Media player %s not loaded yet; delaying load",
                    hide_email(account),
                    hide_serial(key),
                )
                return False
    result = hass.data[DATA_ALEXAMEDIA]["notify_service"] = AlexaNotificationService(
        hass
    )
    return result


async def async_unload_entry(hass, entry) -> bool:
    """Unload a config entry."""
    _LOGGER.debug("Attempting to unload notify")
    target_account = entry.data[CONF_EMAIL]
    other_accounts = False
    for account, account_dict in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
        if account == target_account:
            if "entities" not in account_dict:
                continue
            for device in account_dict["entities"]["media_player"].values():
                if device.entity_id:
                    entity_id = device.entity_id.split(".")
                    hass.services.async_remove(
                        SERVICE_NOTIFY, f"{DOMAIN}_{entity_id[1]}"
                    )
        else:
            other_accounts = True
    if not other_accounts:
        hass.services.async_remove(SERVICE_NOTIFY, f"{DOMAIN}")
        if hass.data[DATA_ALEXAMEDIA].get("notify_service"):
            hass.data[DATA_ALEXAMEDIA].pop("notify_service")
    return True


class AlexaNotificationService(BaseNotificationService):
    """Implement Alexa Media Player notification service."""

    def __init__(self, hass):
        """Initialize the service."""
        self.hass = hass
        self.last_called = True

    def convert(self, names, type_="entities", filter_matches=False):
        """Return a list of converted Alexa devices based on names.

        Names may be matched either by serialNumber, accountName, or
        Homeassistant entity_id and can return any of the above plus entities

        Parameters
        ----------
        names : list(string)
            A list of names to convert
        type_ : string
            The type to return entities, entity_ids, serialnumbers, names
        filter_matches : bool
            Whether non-matching items are removed from the returned list.

        Returns
        -------
        list(string)
            List of home assistant entity_ids

        """
        devices = []
        if isinstance(names, str):
            names = [names]
        for item in names:
            matched = False
            for alexa in self.devices:
                # _LOGGER.debug(
                #     "Testing item: %s against (%s, %s, %s, %s)",
                #     item,
                #     alexa,
                #     alexa.name,
                #     hide_serial(alexa.unique_id),
                #     alexa.entity_id,
                # )
                if item in (
                    alexa,
                    alexa.name,
                    alexa.unique_id,
                    alexa.entity_id,
                    alexa.device_serial_number,
                ):
                    if type_ == "entities":
                        converted = alexa
                    elif type_ == "serialnumbers":
                        converted = alexa.device_serial_number
                    elif type_ == "names":
                        converted = alexa.name
                    elif type_ == "entity_ids":
                        converted = alexa.entity_id
                    devices.append(converted)
                    matched = True
                    # _LOGGER.debug("Converting: %s to (%s): %s", item, type_, converted)
            if not filter_matches and not matched:
                devices.append(item)
        return devices

    @property
    def targets(self):
        """Return a dictionary of Alexa devices."""
        devices = {}
        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
            if "entities" not in account_dict:
                continue
            last_called_entity = None
            for _, entity in account_dict["entities"]["media_player"].items():
                if entity is None or entity.entity_id is None:
                    continue
                entity_name = (entity.entity_id).split(".")[1]
                devices[entity_name] = entity.unique_id
                if self.last_called and entity.extra_state_attributes.get(
                    "last_called"
                ):
                    attrs = entity.extra_state_attributes
                    try:
                        ts = int(attrs.get("last_called_timestamp") or 0)
                    except (TypeError, ValueError):
                        ts = 0
                    if last_called_entity is None:
                        last_called_entity = entity
                    else:
                        best_attrs = last_called_entity.extra_state_attributes
                        try:
                            best_ts = int(best_attrs.get("last_called_timestamp") or 0)
                        except (TypeError, ValueError):
                            best_ts = 0
                        if ts > best_ts:
                            last_called_entity = entity
            if last_called_entity is not None:
                entity_name = (last_called_entity.entity_id).split(".")[1]
                entity_name_last_called = (
                    f"last_called{'_'+ email if entity_name[-1:].isdigit() else ''}"
                )
                devices[entity_name_last_called] = last_called_entity.unique_id
        return devices

    @property
    def devices(self):
        """Return a list of Alexa devices."""
        devices = []
        if (
            "accounts" not in self.hass.data[DATA_ALEXAMEDIA]
            or not self.hass.data[DATA_ALEXAMEDIA]["accounts"].items()
        ):
            return devices
        for _, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
            devices = devices + list(account_dict["entities"]["media_player"].values())
        return devices

    async def async_send_message(self, message="", **kwargs):
        # pylint: disable=too-many-branches
        """Send a message to an Alexa device."""
        _LOGGER.debug("Message: %s, kwargs: %s", message, kwargs)
        _LOGGER.debug("Target type: %s", type(kwargs.get(ATTR_TARGET)))
        kwargs["message"] = message
        targets = kwargs.get(ATTR_TARGET)
        title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
        data = kwargs.get(ATTR_DATA, {})
        data = data if data is not None else {}
        if isinstance(targets, str):
            try:
                targets = json.loads(targets)
            except json.JSONDecodeError:
                _LOGGER.error("Target must be a valid json")
                return
        processed_targets = []
        for target in targets:
            _LOGGER.debug("Processing: %s", target)
            if not isinstance(target, str):
                processed_targets.append(target)
                _LOGGER.debug("Processed non-string target: %s", processed_targets)
                continue
            try:
                parsed = json.loads(target)
                if isinstance(parsed, list):
                    processed_targets.extend(parsed)
                else:
                    processed_targets.append(parsed)
                _LOGGER.debug("Processed Target by json: %s", processed_targets)
            except json.JSONDecodeError:
                if "," in target:
                    processed_targets += [
                        item.strip() for item in target.split(",") if item.strip()
                    ]
                else:
                    processed_targets.append(target.strip())
                _LOGGER.debug("Processed Target by string: %s", processed_targets)
        # Expand Home Assistant group targets into member entity IDs before
        # passing to convert(). The convert() method resolves Alexa-specific
        # identifiers (entity_id, name, serial), but it does not expand HA groups.
        #
        # Supported group forms:
        # - media_player.* helper groups with an entity_id attribute
        # - old-style YAML group.* entities, via expand_entity_ids()
        #
        # Expansion happens here, while targets are still plain strings. Do not run
        # expand_entity_ids() after convert(), because convert() returns Alexa
        # objects, not entity ID strings.
        expanded_targets = []

        for target in processed_targets:
            if not isinstance(target, str):
                expanded_targets.append(target)
                continue

            # UI media_player group helper
            if (
                target.startswith("media_player.")
                and (state := self.hass.states.get(target)) is not None
                and "entity_id" in state.attributes
            ):
                members = state.attributes["entity_id"]
                if isinstance(members, (list, tuple)):
                    expanded_targets.extend(members)
                else:
                    expanded_targets.append(target)
                continue

            # Old-style YAML group.*, expand before convert()
            if target.startswith("group."):
                try:
                    expanded_targets.extend(expand_entity_ids(self.hass, [target]))
                except ValueError:
                    _LOGGER.debug("Invalid Home Assistant group target: %s", target)
                    expanded_targets.append(target)
                continue

            expanded_targets.append(target)

        entities = self.convert(expanded_targets, type_="entities")
        tasks = []
        for account, account_dict in self.hass.data[DATA_ALEXAMEDIA][
            "accounts"
        ].items():
            data_type = data.get("type", "tts")
            for alexa in account_dict["entities"]["media_player"].values():
                if data_type == "tts":
                    targets = self.convert(
                        entities, type_="entities", filter_matches=True
                    )
                    # _LOGGER.debug("TTS entities: %s", targets)
                    if alexa in targets and alexa.available:
                        _LOGGER.debug("TTS by %s : %s", alexa, message)
                        tasks.append(
                            alexa.async_send_tts(
                                message,
                                queue_delay=self.hass.data[DATA_ALEXAMEDIA]["accounts"][
                                    account
                                ]["options"].get(CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY),
                            )
                        )
                elif data_type == "announce":
                    targets = self.convert(
                        entities, type_="serialnumbers", filter_matches=True
                    )
                    # _LOGGER.debug(
                    #     "Announce targets: %s entities: %s",
                    #     list(map(hide_serial, targets)),
                    #     entities,
                    # )
                    if alexa.device_serial_number in targets and alexa.available:
                        _LOGGER.debug(
                            ("%s: Announce by %s to targets: %s: %s"),
                            hide_email(account),
                            alexa,
                            list(map(hide_serial, targets)),
                            message,
                        )
                        tasks.append(
                            alexa.async_send_announcement(
                                message,
                                targets=targets,
                                title=title,
                                method=(data["method"] if "method" in data else "all"),
                                queue_delay=self.hass.data[DATA_ALEXAMEDIA]["accounts"][
                                    account
                                ]["options"].get(CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY),
                            )
                        )
                        break
                elif data_type == "push":
                    targets = self.convert(
                        entities, type_="entities", filter_matches=True
                    )
                    if alexa in targets and alexa.available:
                        _LOGGER.debug("Push by %s: %s %s", alexa, title, message)
                        tasks.append(
                            alexa.async_send_mobilepush(
                                message,
                                title=title,
                                queue_delay=self.hass.data[DATA_ALEXAMEDIA]["accounts"][
                                    account
                                ]["options"].get(CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY),
                            )
                        )
                elif data_type == "dropin_notification":
                    targets = self.convert(
                        entities, type_="entities", filter_matches=True
                    )
                    if alexa in targets and alexa.available:
                        _LOGGER.debug(
                            "Notification dropin by %s: %s %s", alexa, title, message
                        )
                        tasks.append(
                            alexa.async_send_dropin_notification(
                                message,
                                title=title,
                                queue_delay=self.hass.data[DATA_ALEXAMEDIA]["accounts"][
                                    account
                                ]["options"].get(CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY),
                            )
                        )
                else:
                    errormessage = (
                        f"{account}: Data value `type={data_type}` is not implemented. "
                        f"See {NOTIFY_URL}"
                    )
                    _LOGGER.debug(errormessage)
                    raise vol.Invalid(errormessage)
        await asyncio.gather(*tasks)
