"""Code to manage fetching LIVISI data API."""
from __future__ import annotations

from datetime import timedelta
from typing import Any

from aiohttp import ClientConnectorError
from aiolivisi import AioLivisi, LivisiEvent, Websocket

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import (
    AVATAR_PORT,
    CLASSIC_PORT,
    CONF_HOST,
    CONF_PASSWORD,
    DEVICE_POLLING_DELAY,
    LIVISI_REACHABILITY_CHANGE,
    LIVISI_STATE_CHANGE,
    LOGGER,
)


class LivisiDataUpdateCoordinator(DataUpdateCoordinator[list[dict[str, Any]]]):
    """Class to manage fetching LIVISI data API."""

    config_entry: ConfigEntry

    def __init__(
        self, hass: HomeAssistant, config_entry: ConfigEntry, aiolivisi: AioLivisi
    ) -> None:
        """Initialize my coordinator."""
        super().__init__(
            hass,
            LOGGER,
            name="Livisi devices",
            update_interval=timedelta(seconds=DEVICE_POLLING_DELAY),
        )
        self.config_entry = config_entry
        self.hass = hass
        self.aiolivisi = aiolivisi
        self.websocket = Websocket(aiolivisi)
        self.devices: set[str] = set()
        self.rooms: dict[str, Any] = {}
        self.serial_number: str = ""
        self.controller_type: str = ""
        self.is_avatar: bool = False
        self.port: int = 0

    async def _async_update_data(self) -> list[dict[str, Any]]:
        """Get device configuration from LIVISI."""
        try:
            return await self.async_get_devices()
        except ClientConnectorError as exc:
            raise UpdateFailed("Failed to get LIVISI the devices") from exc

    async def async_setup(self) -> None:
        """Set up the Livisi Smart Home Controller."""
        if not self.aiolivisi.livisi_connection_data:
            livisi_connection_data = {
                "ip_address": self.config_entry.data[CONF_HOST],
                "password": self.config_entry.data[CONF_PASSWORD],
            }

            await self.aiolivisi.async_set_token(
                livisi_connection_data=livisi_connection_data
            )
        controller_data = await self.aiolivisi.async_get_controller()
        if controller_data["controllerType"] == "Avatar":
            self.port = AVATAR_PORT
            self.is_avatar = True
        else:
            self.port = CLASSIC_PORT
            self.is_avatar = False
        self.serial_number = controller_data["serialNumber"]
        self.controller_type = controller_data["controllerType"]

    async def async_get_devices(self) -> list[dict[str, Any]]:
        """Set the discovered devices list."""
        return await self.aiolivisi.async_get_devices()

    async def async_get_pss_state(self, capability: str) -> bool | None:
        """Set the PSS state."""
        response: dict[str, Any] = await self.aiolivisi.async_get_pss_state(
            capability[1:]
        )
        if response is None:
            return None
        on_state = response["onState"]
        return on_state["value"]

    async def async_set_all_rooms(self) -> None:
        """Set the room list."""
        response: list[dict[str, Any]] = await self.aiolivisi.async_get_all_rooms()

        for available_room in response:
            available_room_config: dict[str, Any] = available_room["config"]
            self.rooms[available_room["id"]] = available_room_config["name"]

    def on_data(self, event_data: LivisiEvent) -> None:
        """Define a handler to fire when the data is received."""
        if event_data.onState is not None:
            async_dispatcher_send(
                self.hass,
                f"{LIVISI_STATE_CHANGE}_{event_data.source}",
                event_data.onState,
            )
        if event_data.isReachable is not None:
            async_dispatcher_send(
                self.hass,
                f"{LIVISI_REACHABILITY_CHANGE}_{event_data.source}",
                event_data.isReachable,
            )

    async def on_close(self) -> None:
        """Define a handler to fire when the websocket is closed."""
        for device_id in self.devices:
            is_reachable: bool = False
            async_dispatcher_send(
                self.hass,
                f"{LIVISI_REACHABILITY_CHANGE}_{device_id}",
                is_reachable,
            )

        await self.websocket.connect(self.on_data, self.on_close, self.port)

    async def ws_connect(self) -> None:
        """Connect the websocket."""
        await self.websocket.connect(self.on_data, self.on_close, self.port)
