"""Camera platform for Xthings Cloud."""

from typing import Any, override

from aiohttp import ClientError
from ha_xthings_cloud import KvsSignalingClient, XthingsCloudApiError
from webrtc_models import RTCIceCandidateInit

from homeassistant.components.camera import (
    Camera,
    CameraEntityFeature,
    WebRTCAnswer,
    WebRTCCandidate,
    WebRTCError,
    WebRTCSendMessage,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, LOGGER
from .coordinator import XthingsCloudConfigEntry, XthingsCloudCoordinator

KVS_EXCEPTIONS = (
    XthingsCloudApiError,
    ClientError,
    TimeoutError,
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: XthingsCloudConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up camera platform."""
    coordinator = entry.runtime_data
    entities = [
        XthingsCloudCamera(coordinator, device_id, device_data)
        for device_id, device_data in coordinator.data.items()
        if device_data["type"] == "camera"
    ]
    async_add_entities(entities)


class XthingsCloudCamera(CoordinatorEntity[XthingsCloudCoordinator], Camera):
    """Xthings Cloud camera entity."""

    _attr_has_entity_name = True
    _attr_name = None
    _attr_supported_features = CameraEntityFeature.STREAM

    def __init__(
        self,
        coordinator: XthingsCloudCoordinator,
        device_id: str,
        device_data: dict[str, Any],
    ) -> None:
        """Initialize the camera."""
        CoordinatorEntity.__init__(self, coordinator)
        Camera.__init__(self)
        self._device_id = device_id
        self._attr_unique_id = device_id
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, device_id)},
            name=device_data["name"],
            manufacturer="Xthings",
            model=device_data["model"],
            sw_version=device_data.get("version"),
        )
        self._cached_image: bytes | None = None
        self._cached_snapshot_url: str | None = None
        self._kvs_sessions: dict[str, KvsSignalingClient] = {}
        self._pending_candidates: dict[str, list[RTCIceCandidateInit]] = {}
        self._open_sessions: set[str] = set()

    @property
    def device_data(self) -> dict[str, Any]:
        """Return current device data."""
        return self.coordinator.data[self._device_id]

    @property
    @override
    def is_on(self) -> bool:
        """Return true if on."""
        return True

    @property
    @override
    def available(self) -> bool:
        """Return true if device is available."""
        return (
            super().available
            and self._device_id in self.coordinator.data
            and self.device_data["online"]
        )

    @override
    async def async_camera_image(
        self, width: int | None = None, height: int | None = None
    ) -> bytes | None:
        """Return camera snapshot image."""
        snapshot_url = self.device_data.get("status", {}).get("snapshot_url")
        if not snapshot_url:
            return None
        if snapshot_url == self._cached_snapshot_url and self._cached_image:
            return self._cached_image
        image = await self._async_fetch_image(snapshot_url)
        if image:
            self._cached_image = image
            self._cached_snapshot_url = snapshot_url
        return image

    async def _async_fetch_image(self, url: str) -> bytes | None:
        """Fetch a snapshot image from the given URL, returning None on failure."""
        try:
            return await self.coordinator.client.async_get_snapshot(url)
        except XthingsCloudApiError as err:
            LOGGER.debug("Failed to fetch camera snapshot from %s: %s", url, err)
        return None

    @override
    async def async_will_remove_from_hass(self) -> None:
        """Clean up tasks and sessions when entity is removed."""
        for session_id in list(self._kvs_sessions):
            await self.async_close_webrtc_session(session_id)

        self._pending_candidates.clear()
        await super().async_will_remove_from_hass()

    @override
    async def async_handle_async_webrtc_offer(
        self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
    ) -> None:
        """Handle WebRTC offer via KVS signaling."""
        if session_id in self._kvs_sessions:
            await self.async_close_webrtc_session(session_id)
        self._open_sessions.add(session_id)

        try:
            kvs_data = await self.coordinator.client.async_get_camera_webrtc(
                self._device_id
            )
            if session_id not in self._open_sessions:
                return

            region = kvs_data.get("region")
            channel_arn = kvs_data.get("channel_arn")
            viewer = kvs_data.get("viewer")

            session = async_get_clientsession(self.hass)
            kvs_client = KvsSignalingClient(
                session=session,
                region=region,
                channel_arn=channel_arn,
                credentials=viewer,
            )
            self._kvs_sessions[session_id] = kvs_client

            def _on_ice(cand: dict[str, Any]) -> None:
                if session_id not in self._open_sessions:
                    return

                send_message(
                    WebRTCCandidate(
                        candidate=RTCIceCandidateInit(
                            candidate=cand["candidate"],
                            sdp_mid=cand.get("sdpMid"),
                            sdp_m_line_index=cand.get("sdpMLineIndex"),
                        )
                    )
                )

            answer_sdp = await kvs_client.async_get_answer_sdp(
                offer_sdp,
                on_ice_candidate=_on_ice,
            )
            if session_id not in self._open_sessions:
                return

            pending_candidates = self._pending_candidates.get(session_id, [])
            for cand in pending_candidates:
                if session_id not in self._open_sessions:
                    return
                try:
                    await kvs_client.async_send_ice_candidate(
                        candidate=cand.candidate,
                        sdp_mid=cand.sdp_mid,
                        sdp_m_line_index=cand.sdp_m_line_index,
                    )
                except KVS_EXCEPTIONS as err:
                    LOGGER.warning("Failed to send cached ICE candidate: %s", err)

            if session_id not in self._open_sessions:
                return

            # Clear candidates only after attempting to send them all
            self._pending_candidates.pop(session_id, None)

            send_message(WebRTCAnswer(answer=answer_sdp))

        except KVS_EXCEPTIONS as err:
            if session_id not in self._open_sessions:
                return
            LOGGER.exception("KVS WebRTC failed: %s", err)
            send_message(
                WebRTCError(code="kvs_error", message="WebRTC negotiation failed")
            )
            await self.async_close_webrtc_session(session_id)

    @override
    async def async_on_webrtc_candidate(
        self, session_id: str, candidate: RTCIceCandidateInit
    ) -> None:
        """Forward ICE candidate to KVS signaling channel."""
        if session_id not in self._open_sessions:
            LOGGER.debug(
                "KVS: Ignoring ICE candidate for unknown session %s", session_id
            )
            return

        kvs_client = self._kvs_sessions.get(session_id)
        if kvs_client:
            try:
                await kvs_client.async_send_ice_candidate(
                    candidate=candidate.candidate,
                    sdp_mid=candidate.sdp_mid,
                    sdp_m_line_index=candidate.sdp_m_line_index,
                )
            except KVS_EXCEPTIONS as err:
                LOGGER.warning("Failed to send ICE candidate: %s", err)
        else:
            candidates = self._pending_candidates.setdefault(session_id, [])
            candidates.append(candidate)
            LOGGER.debug("KVS: Cached ICE candidate for session %s", session_id)

    def _remove_webrtc_session(self, session_id: str) -> KvsSignalingClient | None:
        """Remove WebRTC session state and return the KVS client."""
        self._open_sessions.discard(session_id)
        self._pending_candidates.pop(session_id, None)
        return self._kvs_sessions.pop(session_id, None)

    @override
    def close_webrtc_session(self, session_id: str) -> None:
        """Close WebRTC session and clean up KVS signaling."""
        kvs_client = self._remove_webrtc_session(session_id)
        if kvs_client:
            self.hass.async_create_task(self._async_close_kvs_client(kvs_client))

    async def async_close_webrtc_session(self, session_id: str) -> None:
        """Close WebRTC session and await KVS signaling cleanup."""
        kvs_client = self._remove_webrtc_session(session_id)
        if kvs_client:
            await self._async_close_kvs_client(kvs_client)

    async def _async_close_kvs_client(self, kvs_client: KvsSignalingClient) -> None:
        """Close a KVS signaling client and log close failures."""
        try:
            await kvs_client.async_close()
        except KVS_EXCEPTIONS as err:
            LOGGER.warning("Failed to close KVS WebRTC session: %s", err)
