"""Tests for Xthings Cloud camera platform."""

import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

from aiohttp import ClientError
from ha_xthings_cloud import XthingsCloudApiError
import pytest
from syrupy.assertion import SnapshotAssertion
from webrtc_models import RTCIceCandidateInit

from homeassistant.components.camera import (
    WebRTCAnswer,
    WebRTCError,
    async_get_image,
    get_camera_from_entity_id,
)
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er

from . import get_device_by_id, setup_integration

from tests.common import MockConfigEntry, snapshot_platform


async def setup_camera_integration(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Set up the integration with only the camera platform."""
    with patch("homeassistant.components.xthings_cloud.PLATFORMS", [Platform.CAMERA]):
        await setup_integration(hass, mock_config_entry)


async def test_cameras(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
    entity_registry: er.EntityRegistry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test camera entities are created correctly."""
    with patch("homeassistant.components.camera._RND.getrandbits", return_value=1):
        await setup_camera_integration(hass, mock_config_entry)

    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)


async def test_camera_unavailable_when_offline(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test camera shows unavailable when device is offline."""
    get_device_by_id(mock_api_client, "dev_camera_001")["online"] = False
    await setup_camera_integration(hass, mock_config_entry)

    state = hass.states.get("camera.front_door_camera")
    assert state is not None
    assert state.state == STATE_UNAVAILABLE


async def test_camera_image(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test camera image fetching."""
    mock_api_client.async_get_snapshot.return_value = b"image_data"
    await setup_camera_integration(hass, mock_config_entry)

    state = hass.states.get("camera.front_door_camera")
    assert state is not None

    image = await async_get_image(hass, "camera.front_door_camera")
    assert image.content == b"image_data"
    mock_api_client.async_get_snapshot.assert_called_once_with(
        "https://example.com/snapshot.jpg"
    )


async def test_camera_image_fetch_error_returns_no_image(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test camera image fetch errors return no image."""
    mock_api_client.async_get_snapshot.return_value = b"image_data"
    await setup_camera_integration(hass, mock_config_entry)

    image = await async_get_image(hass, "camera.front_door_camera")
    assert image.content == b"image_data"

    get_device_by_id(mock_api_client, "dev_camera_001")["status"]["snapshot_url"] = (
        "https://example.com/new_snapshot.jpg"
    )
    mock_api_client.async_get_snapshot.side_effect = XthingsCloudApiError(
        "Request failed"
    )

    with pytest.raises(HomeAssistantError, match="Unable to get image"):
        await async_get_image(hass, "camera.front_door_camera")
    mock_api_client.async_get_snapshot.assert_called_with(
        "https://example.com/new_snapshot.jpg"
    )


async def test_updating_state_updates_snapshot_url(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
    mock_websocket: AsyncMock,
) -> None:
    """Test updating state uses the new snapshot URL on the next image request."""
    mock_api_client.async_get_snapshot.return_value = b"new_image_data"
    await setup_camera_integration(hass, mock_config_entry)

    assert mock_websocket.call_args is not None

    mock_websocket.call_args[1]["on_device_status"](
        "dev_camera_001",
        {
            "snapshot_url": "https://example.com/new_snapshot.jpg",
        },
    )

    await hass.async_block_till_done()

    mock_api_client.async_get_snapshot.assert_not_called()

    image = await async_get_image(hass, "camera.front_door_camera")
    assert image.content == b"new_image_data"
    mock_api_client.async_get_snapshot.assert_called_once_with(
        "https://example.com/new_snapshot.jpg"
    )


async def test_webrtc_offer(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test WebRTC offer handling."""
    mock_api_client.async_get_camera_webrtc.return_value = {
        "region": "us-east-1",
        "channel_arn": "arn:aws:kinesisvideo:us-east-1:111111111111:channel/test/123",
        "viewer": {
            "AccessKeyId": "test",
            "SecretAccessKey": "test",
            "SessionToken": "test",
        },
    }
    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    with patch(
        "homeassistant.components.xthings_cloud.camera.KvsSignalingClient"
    ) as mock_kvs_client_class:
        mock_kvs_client = mock_kvs_client_class.return_value

        async def mock_get_answer_sdp(_offer_sdp, on_ice_candidate):
            on_ice_candidate(
                {
                    "candidate": "mock_candidate_string",
                    "sdpMid": "mock_sdp_mid",
                    "sdpMLineIndex": 0,
                }
            )
            return "mock_answer_sdp"

        mock_kvs_client.async_get_answer_sdp = AsyncMock(
            side_effect=mock_get_answer_sdp
        )

        mock_send_message = MagicMock()

        await camera_entity.async_handle_async_webrtc_offer(
            offer_sdp="mock_offer_sdp",
            session_id="mock_session_id",
            send_message=mock_send_message,
        )

        mock_api_client.async_get_camera_webrtc.assert_called_once_with(
            "dev_camera_001"
        )
        mock_kvs_client_class.assert_called_once()
        mock_kvs_client.async_get_answer_sdp.assert_called_once()

        assert mock_send_message.call_count == 2
        call_arg = mock_send_message.call_args[0][0]
        assert isinstance(call_arg, WebRTCAnswer)
        assert call_arg.answer == "mock_answer_sdp"


async def test_webrtc_candidate_caching_and_flush(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test ICE candidate caching and flushing on offer."""
    kvs_data = {
        "region": "us-east-1",
        "channel_arn": "arn:aws:kinesisvideo:us-east-1:111111111111:channel/test/123",
        "viewer": {
            "AccessKeyId": "test",
            "SecretAccessKey": "test",
            "SessionToken": "test",
        },
    }
    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    session_id = "mock_session_id"
    candidate = RTCIceCandidateInit(
        candidate="mock_candidate_string",
        sdp_mid="mock_sdp_mid",
        sdp_m_line_index=0,
    )

    async def mock_get_camera_webrtc(_device_id):
        await camera_entity.async_on_webrtc_candidate(session_id, candidate)
        return kvs_data

    mock_api_client.async_get_camera_webrtc.side_effect = mock_get_camera_webrtc

    with patch(
        "homeassistant.components.xthings_cloud.camera.KvsSignalingClient"
    ) as mock_kvs_client_class:
        mock_kvs_client = mock_kvs_client_class.return_value
        mock_kvs_client.async_get_answer_sdp = AsyncMock(return_value="mock_answer_sdp")
        mock_kvs_client.async_send_ice_candidate = AsyncMock()
        mock_kvs_client.async_close = AsyncMock()

        await camera_entity.async_handle_async_webrtc_offer(
            offer_sdp="mock_offer_sdp",
            session_id=session_id,
            send_message=MagicMock(),
        )

        # Verify candidate was flushed to KVS client
        mock_kvs_client.async_send_ice_candidate.assert_called_once_with(
            candidate="mock_candidate_string",
            sdp_mid="mock_sdp_mid",
            sdp_m_line_index=0,
        )

        # Re-send candidate after session is established to verify direct send
        await camera_entity.async_on_webrtc_candidate(session_id, candidate)
        assert mock_kvs_client.async_send_ice_candidate.call_count == 2

        mock_kvs_client.async_send_ice_candidate.side_effect = ClientError(
            "Send failed"
        )
        await camera_entity.async_on_webrtc_candidate(session_id, candidate)

        await hass.async_block_till_done()
        mock_kvs_client.async_close.assert_not_called()

        mock_kvs_client.async_send_ice_candidate.side_effect = None
        await camera_entity.async_on_webrtc_candidate(session_id, candidate)
        assert mock_kvs_client.async_send_ice_candidate.call_count == 4


async def test_webrtc_session_cleanup(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test WebRTC session cleanup clears cache and closes client."""
    mock_api_client.async_get_camera_webrtc.return_value = {
        "region": "us-east-1",
        "channel_arn": "arn:aws:kinesisvideo:us-east-1:111111111111:channel/test/123",
        "viewer": {
            "AccessKeyId": "test",
            "SecretAccessKey": "test",
            "SessionToken": "test",
        },
    }
    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    session_id = "mock_session_id"
    candidate = RTCIceCandidateInit(
        candidate="mock_candidate_string",
        sdp_mid="mock_sdp_mid",
        sdp_m_line_index=0,
    )

    with patch(
        "homeassistant.components.xthings_cloud.camera.KvsSignalingClient"
    ) as mock_kvs_client_class:
        mock_kvs_client = mock_kvs_client_class.return_value
        mock_kvs_client.async_get_answer_sdp = AsyncMock(return_value="mock_answer_sdp")
        mock_kvs_client.async_send_ice_candidate = AsyncMock()
        mock_kvs_client.async_close = AsyncMock()

        await camera_entity.async_handle_async_webrtc_offer(
            offer_sdp="mock_offer_sdp",
            session_id=session_id,
            send_message=MagicMock(),
        )

        # Test close_webrtc_session
        camera_entity.close_webrtc_session(session_id)

        await hass.async_block_till_done()
        mock_kvs_client.async_close.assert_called_once()

        # Verify that candidate is not flushed after session is closed
        mock_kvs_client.async_send_ice_candidate = AsyncMock()
        await camera_entity.async_on_webrtc_candidate(session_id, candidate)
        mock_kvs_client.async_send_ice_candidate.assert_not_called()

        # Verify a new offer with the same session id accepts candidates again
        await camera_entity.async_handle_async_webrtc_offer(
            offer_sdp="mock_offer_sdp",
            session_id=session_id,
            send_message=MagicMock(),
        )
        await camera_entity.async_on_webrtc_candidate(session_id, candidate)
        mock_kvs_client.async_send_ice_candidate.assert_called_once_with(
            candidate="mock_candidate_string",
            sdp_mid="mock_sdp_mid",
            sdp_m_line_index=0,
        )


async def test_webrtc_offer_closed_while_fetching_credentials(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test closing a session while the offer is awaiting credentials."""
    credentials_requested = asyncio.Event()
    resume_credentials = asyncio.Event()

    async def mock_get_camera_webrtc(_device_id):
        credentials_requested.set()
        await resume_credentials.wait()
        return {
            "region": "us-east-1",
            "channel_arn": (
                "arn:aws:kinesisvideo:us-east-1:111111111111:channel/test/123"
            ),
            "viewer": {
                "AccessKeyId": "test",
                "SecretAccessKey": "test",
                "SessionToken": "test",
            },
        }

    mock_api_client.async_get_camera_webrtc.side_effect = mock_get_camera_webrtc

    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    session_id = "mock_session_id"
    mock_send_message = MagicMock()

    with patch(
        "homeassistant.components.xthings_cloud.camera.KvsSignalingClient"
    ) as mock_kvs_client_class:
        offer_task = hass.async_create_task(
            camera_entity.async_handle_async_webrtc_offer(
                offer_sdp="mock_offer_sdp",
                session_id=session_id,
                send_message=mock_send_message,
            )
        )

        await credentials_requested.wait()
        camera_entity.close_webrtc_session(session_id)
        resume_credentials.set()
        await offer_task

        mock_kvs_client_class.assert_not_called()
        mock_send_message.assert_not_called()


async def test_webrtc_session_cleanup_on_entity_removal(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test WebRTC sessions are awaited during entity removal."""
    mock_api_client.async_get_camera_webrtc.return_value = {
        "region": "us-east-1",
        "channel_arn": "arn:aws:kinesisvideo:us-east-1:111111111111:channel/test/123",
        "viewer": {
            "AccessKeyId": "test",
            "SecretAccessKey": "test",
            "SessionToken": "test",
        },
    }
    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    with patch(
        "homeassistant.components.xthings_cloud.camera.KvsSignalingClient"
    ) as mock_kvs_client_class:
        mock_kvs_client = mock_kvs_client_class.return_value
        mock_kvs_client.async_get_answer_sdp = AsyncMock(return_value="mock_answer_sdp")
        mock_kvs_client.async_close = AsyncMock()

        await camera_entity.async_handle_async_webrtc_offer(
            offer_sdp="mock_offer_sdp",
            session_id="mock_session_id",
            send_message=MagicMock(),
        )

        assert await hass.config_entries.async_unload(mock_config_entry.entry_id)

        mock_kvs_client.async_close.assert_awaited_once()


async def test_webrtc_offer_kvs_error(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test WebRTC offer handling when the KVS library raises."""
    mock_api_client.async_get_camera_webrtc.return_value = {
        "region": "us-east-1",
        "channel_arn": None,
    }
    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    mock_send_message = MagicMock()

    with patch(
        "homeassistant.components.xthings_cloud.camera.KvsSignalingClient",
        side_effect=XthingsCloudApiError("Invalid KVS credentials"),
    ):
        await camera_entity.async_handle_async_webrtc_offer(
            offer_sdp="mock_offer_sdp",
            session_id="mock_session_id",
            send_message=mock_send_message,
        )

    mock_send_message.assert_called_once()
    call_arg = mock_send_message.call_args[0][0]
    assert isinstance(call_arg, WebRTCError)
    assert call_arg.code == "kvs_error"
    assert call_arg.message == "WebRTC negotiation failed"


async def test_webrtc_offer_unexpected_error(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_api_client: AsyncMock,
) -> None:
    """Test WebRTC offer handling when an unexpected error occurs."""
    mock_api_client.async_get_camera_webrtc.side_effect = XthingsCloudApiError(
        "KVS failed"
    )
    await setup_camera_integration(hass, mock_config_entry)

    camera_entity = get_camera_from_entity_id(hass, "camera.front_door_camera")
    assert camera_entity is not None

    mock_send_message = MagicMock()

    await camera_entity.async_handle_async_webrtc_offer(
        offer_sdp="mock_offer_sdp",
        session_id="mock_session_id",
        send_message=mock_send_message,
    )

    mock_send_message.assert_called_once()
    call_arg = mock_send_message.call_args[0][0]
    assert isinstance(call_arg, WebRTCError)
    assert call_arg.code == "kvs_error"
    assert call_arg.message == "WebRTC negotiation failed"
