"""Support for the Netatmo cameras."""
from __future__ import annotations

import logging
from typing import Any, cast

import aiohttp
from pyatmo import ApiError as NetatmoApiError, modules as NaModules
from pyatmo.event import Event as NaEvent
import voluptuous as vol

from homeassistant.components.camera import Camera, CameraEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import (
    ATTR_CAMERA_LIGHT_MODE,
    ATTR_PERSON,
    ATTR_PERSONS,
    CAMERA_LIGHT_MODES,
    CONF_URL_SECURITY,
    DATA_CAMERAS,
    DATA_EVENTS,
    DOMAIN,
    EVENT_TYPE_LIGHT_MODE,
    EVENT_TYPE_OFF,
    EVENT_TYPE_ON,
    MANUFACTURER,
    NETATMO_CREATE_CAMERA,
    SERVICE_SET_CAMERA_LIGHT,
    SERVICE_SET_PERSON_AWAY,
    SERVICE_SET_PERSONS_HOME,
    WEBHOOK_LIGHT_MODE,
    WEBHOOK_NACAMERA_CONNECTION,
    WEBHOOK_PUSH_TYPE,
)
from .data_handler import EVENT, HOME, SIGNAL_NAME, NetatmoDevice
from .netatmo_entity_base import NetatmoBase

_LOGGER = logging.getLogger(__name__)

DEFAULT_QUALITY = "high"


async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Set up the Netatmo camera platform."""

    @callback
    def _create_entity(netatmo_device: NetatmoDevice) -> None:
        entity = NetatmoCamera(netatmo_device)
        async_add_entities([entity])

    entry.async_on_unload(
        async_dispatcher_connect(hass, NETATMO_CREATE_CAMERA, _create_entity)
    )

    platform = entity_platform.async_get_current_platform()

    platform.async_register_entity_service(
        SERVICE_SET_PERSONS_HOME,
        {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])},
        "_service_set_persons_home",
    )
    platform.async_register_entity_service(
        SERVICE_SET_PERSON_AWAY,
        {vol.Optional(ATTR_PERSON): cv.string},
        "_service_set_person_away",
    )
    platform.async_register_entity_service(
        SERVICE_SET_CAMERA_LIGHT,
        {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)},
        "_service_set_camera_light",
    )


class NetatmoCamera(NetatmoBase, Camera):
    """Representation of a Netatmo camera."""

    _attr_brand = MANUFACTURER
    _attr_has_entity_name = True
    _attr_supported_features = CameraEntityFeature.STREAM

    def __init__(
        self,
        netatmo_device: NetatmoDevice,
    ) -> None:
        """Set up for access to the Netatmo camera images."""
        Camera.__init__(self)
        super().__init__(netatmo_device.data_handler)

        self._camera = cast(NaModules.Camera, netatmo_device.device)
        self._id = self._camera.entity_id
        self._home_id = self._camera.home.entity_id
        self._device_name = self._camera.name
        self._model = self._camera.device_type
        self._config_url = CONF_URL_SECURITY
        self._attr_unique_id = f"{self._id}-{self._model}"
        self._quality = DEFAULT_QUALITY
        self._monitoring: bool | None = None
        self._light_state = None

        self._publishers.extend(
            [
                {
                    "name": HOME,
                    "home_id": self._home_id,
                    SIGNAL_NAME: f"{HOME}-{self._home_id}",
                },
                {
                    "name": EVENT,
                    "home_id": self._home_id,
                    SIGNAL_NAME: f"{EVENT}-{self._home_id}",
                },
            ]
        )

    async def async_added_to_hass(self) -> None:
        """Entity created."""
        await super().async_added_to_hass()

        for event_type in (EVENT_TYPE_LIGHT_MODE, EVENT_TYPE_OFF, EVENT_TYPE_ON):
            self.async_on_remove(
                async_dispatcher_connect(
                    self.hass,
                    f"signal-{DOMAIN}-webhook-{event_type}",
                    self.handle_event,
                )
            )

        self.hass.data[DOMAIN][DATA_CAMERAS][self._id] = self._device_name

    @callback
    def handle_event(self, event: dict) -> None:
        """Handle webhook events."""
        data = event["data"]

        if not data.get("camera_id"):
            return

        if data["home_id"] == self._home_id and data["camera_id"] == self._id:
            if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
                self._attr_is_streaming = False
                self._monitoring = False
            elif data[WEBHOOK_PUSH_TYPE] in (
                "NACamera-on",
                WEBHOOK_NACAMERA_CONNECTION,
            ):
                self._attr_is_streaming = True
                self._monitoring = True
            elif data[WEBHOOK_PUSH_TYPE] == WEBHOOK_LIGHT_MODE:
                self._light_state = data["sub_type"]
                self._attr_extra_state_attributes.update(
                    {"light_state": self._light_state}
                )

            self.async_write_ha_state()
            return

    async def async_camera_image(
        self, width: int | None = None, height: int | None = None
    ) -> bytes | None:
        """Return a still image response from the camera."""
        try:
            return cast(bytes, await self._camera.async_get_live_snapshot())
        except (
            aiohttp.ClientPayloadError,
            aiohttp.ContentTypeError,
            aiohttp.ServerDisconnectedError,
            aiohttp.ClientConnectorError,
            NetatmoApiError,
        ) as err:
            _LOGGER.debug("Could not fetch live camera image (%s)", err)
        return None

    @property
    def supported_features(self) -> int:
        """Return supported features."""
        supported_features: int = CameraEntityFeature.ON_OFF
        if self._model != "NDB":
            supported_features |= CameraEntityFeature.STREAM
        return supported_features

    async def async_turn_off(self) -> None:
        """Turn off camera."""
        await self._camera.async_monitoring_off()

    async def async_turn_on(self) -> None:
        """Turn on camera."""
        await self._camera.async_monitoring_on()

    async def stream_source(self) -> str:
        """Return the stream source."""
        if self._camera.is_local:
            await self._camera.async_update_camera_urls()

        if self._camera.local_url:
            return "{}/live/files/{}/index.m3u8".format(
                self._camera.local_url, self._quality
            )
        return f"{self._camera.vpn_url}/live/files/{self._quality}/index.m3u8"

    @callback
    def async_update_callback(self) -> None:
        """Update the entity's state."""
        self._attr_is_on = self._camera.alim_status is not None
        self._attr_available = self._camera.alim_status is not None

        if self._camera.monitoring is not None:
            self._attr_is_streaming = self._camera.monitoring
            self._attr_motion_detection_enabled = self._camera.monitoring

        self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events(
            self._camera.events
        )

        self._attr_extra_state_attributes.update(
            {
                "id": self._id,
                "monitoring": self._monitoring,
                "sd_status": self._camera.sd_status,
                "alim_status": self._camera.alim_status,
                "is_local": self._camera.is_local,
                "vpn_url": self._camera.vpn_url,
                "local_url": self._camera.local_url,
                "light_state": self._light_state,
            }
        )

    def process_events(self, event_list: list[NaEvent]) -> dict:
        """Add meta data to events."""
        events = {}
        for event in event_list:
            if not (video_id := event.video_id):
                continue
            event_data = event.__dict__
            event_data["subevents"] = [
                event.__dict__
                for event in event_data.get("subevents", [])
                if not isinstance(event, dict)
            ]
            event_data["media_url"] = self.get_video_url(video_id)
            events[event.event_time] = event_data
        return events

    def get_video_url(self, video_id: str) -> str:
        """Get video url."""
        if self._camera.is_local:
            return f"{self._camera.local_url}/vod/{video_id}/files/{self._quality}/index.m3u8"
        return f"{self._camera.vpn_url}/vod/{video_id}/files/{self._quality}/index.m3u8"

    def fetch_person_ids(self, persons: list[str | None]) -> list[str]:
        """Fetch matching person ids for given list of persons."""
        person_ids = []
        person_id_errors = []

        for person in persons:
            person_id = None
            for pid, data in self._camera.home.persons.items():
                if data.pseudo == person:
                    person_ids.append(pid)
                    person_id = pid
                    break

            if person_id is None:
                person_id_errors.append(person)

        if person_id_errors:
            raise HomeAssistantError(f"Person(s) not registered {person_id_errors}")

        return person_ids

    async def _service_set_persons_home(self, **kwargs: Any) -> None:
        """Service to change current home schedule."""
        persons = kwargs.get(ATTR_PERSONS, [])
        person_ids = self.fetch_person_ids(persons)

        await self._camera.home.async_set_persons_home(person_ids=person_ids)
        _LOGGER.debug("Set %s as at home", persons)

    async def _service_set_person_away(self, **kwargs: Any) -> None:
        """Service to mark a person as away or set the home as empty."""
        person = kwargs.get(ATTR_PERSON)
        person_ids = self.fetch_person_ids([person] if person else [])
        person_id = next(iter(person_ids), None)

        await self._camera.home.async_set_persons_away(
            person_id=person_id,
        )

        if person_id:
            _LOGGER.debug("Set %s as away %s", person, person_id)
        else:
            _LOGGER.debug("Set home as empty")

    async def _service_set_camera_light(self, **kwargs: Any) -> None:
        """Service to set light mode."""
        if not isinstance(self._camera, NaModules.netatmo.NOC):
            raise HomeAssistantError(
                f"{self._model} <{self._device_name}> does not have a floodlight"
            )

        mode = str(kwargs.get(ATTR_CAMERA_LIGHT_MODE))
        _LOGGER.debug("Turn %s camera light for '%s'", mode, self._attr_name)
        await self._camera.async_set_floodlight_state(mode)
