"""WebSocket event listener for server state change notifications."""
from __future__ import annotations

import asyncio
import contextlib
import json
import logging

import aiohttp

from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import WS_RECONNECT_DELAY, WS_MAX_RECONNECT_DELAY

_LOGGER = logging.getLogger(__name__)


class EventStreamListener:
    """Listens to server WS endpoint for state change events.

    Triggers a coordinator refresh whenever a target starts or stops processing,
    so HAOS entities react near-instantly to external state changes.
    """

    def __init__(
        self,
        hass: HomeAssistant,
        server_url: str,
        api_key: str,
        coordinator: DataUpdateCoordinator,
    ) -> None:
        self._hass = hass
        self._server_url = server_url
        self._api_key = api_key
        self._coordinator = coordinator
        self._task: asyncio.Task | None = None
        self._shutting_down = False

    async def start(self) -> None:
        """Start listening to the event stream."""
        self._task = self._hass.async_create_background_task(
            self._ws_loop(),
            "wled_screen_controller_events",
        )

    async def _ws_loop(self) -> None:
        """WebSocket connection loop with reconnection."""
        delay = WS_RECONNECT_DELAY
        session = async_get_clientsession(self._hass)
        ws_base = self._server_url.replace("http://", "ws://").replace(
            "https://", "wss://"
        )
        url = f"{ws_base}/api/v1/events/ws?token={self._api_key}"

        while not self._shutting_down:
            try:
                async with session.ws_connect(url) as ws:
                    delay = WS_RECONNECT_DELAY  # reset on successful connect
                    _LOGGER.debug("Event stream connected")
                    async for msg in ws:
                        if msg.type == aiohttp.WSMsgType.TEXT:
                            try:
                                data = json.loads(msg.data)
                            except json.JSONDecodeError:
                                continue
                            if data.get("type") == "state_change":
                                await self._coordinator.async_request_refresh()
                        elif msg.type in (
                            aiohttp.WSMsgType.CLOSED,
                            aiohttp.WSMsgType.ERROR,
                        ):
                            break
            except asyncio.CancelledError:
                raise
            except (aiohttp.ClientError, asyncio.TimeoutError, OSError) as err:
                _LOGGER.debug("Event stream connection error: %s", err)
            except Exception as err:
                _LOGGER.error("Unexpected event stream error: %s", err)

            if self._shutting_down:
                break

            await asyncio.sleep(delay)
            delay = min(delay * 2, WS_MAX_RECONNECT_DELAY)

    async def shutdown(self) -> None:
        """Stop listening."""
        self._shutting_down = True
        if self._task:
            self._task.cancel()
            with contextlib.suppress(asyncio.CancelledError):
                await self._task
            self._task = None
