"""Integration tests for Home Assistant (tool + gateway).

Spins up a real in-process fake HA server (HTTP + WebSocket) and exercises
the full adapter and tool handler paths over real TCP connections.
No mocks -- only real async I/O against a fake server.

Run with:  uv run pytest tests/integration/test_ha_integration.py -v
"""

import asyncio

import pytest

pytestmark = pytest.mark.integration

from unittest.mock import AsyncMock

from gateway.config import Platform, PlatformConfig
from plugins.platforms.homeassistant.adapter import HomeAssistantAdapter
from tests.fakes.fake_ha_server import FakeHAServer, ENTITY_STATES
from tools.homeassistant_tool import (
    _async_call_service,
    _async_get_state,
    _async_list_entities,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _adapter_for(server: FakeHAServer, **extra) -> HomeAssistantAdapter:
    """Create an adapter pointed at the fake server."""
    config = PlatformConfig(
        enabled=True,
        token=server.token,
        extra={"url": server.url, **extra},
    )
    return HomeAssistantAdapter(config)


# ---------------------------------------------------------------------------
# 1. Gateway -- WebSocket lifecycle
# ---------------------------------------------------------------------------


class TestGatewayWebSocket:
    @pytest.mark.asyncio
    async def test_connect_auth_subscribe(self):
        """Full WS handshake succeeds: auth_required -> auth -> auth_ok -> subscribe -> ACK."""
        async with FakeHAServer() as server:
            adapter = _adapter_for(server)
            connected = await adapter.connect()
            assert connected is True
            assert adapter._running is True
            assert adapter._ws is not None
            assert not adapter._ws.closed
            await adapter.disconnect()

    @pytest.mark.asyncio
    async def test_connect_auth_rejected(self):
        """connect() returns False when the server rejects auth."""
        async with FakeHAServer() as server:
            server.reject_auth = True
            adapter = _adapter_for(server)
            connected = await adapter.connect()
            assert connected is False

    @pytest.mark.asyncio
    async def test_event_received_and_forwarded(self):
        """Server pushes event -> adapter calls handle_message with correct MessageEvent."""
        async with FakeHAServer() as server:
            adapter = _adapter_for(server)
            adapter.handle_message = AsyncMock()

            await adapter.connect()

            # Push a state_changed event
            await server.push_event({
                "data": {
                    "entity_id": "light.bedroom",
                    "old_state": {"state": "off", "attributes": {}},
                    "new_state": {
                        "state": "on",
                        "attributes": {"friendly_name": "Bedroom Light"},
                    },
                }
            })

            # Wait for the adapter to process it
            for _ in range(50):
                if adapter.handle_message.call_count > 0:
                    break
                await asyncio.sleep(0.05)

            assert adapter.handle_message.call_count == 1
            msg_event = adapter.handle_message.call_args[0][0]
            assert "Bedroom Light" in msg_event.text
            assert "turned on" in msg_event.text
            assert msg_event.source.platform == Platform.HOMEASSISTANT

            await adapter.disconnect()

    @pytest.mark.asyncio
    async def test_event_filtering_ignores_unwatched(self):
        """Events outside watch_domains are silently dropped."""
        async with FakeHAServer() as server:
            adapter = _adapter_for(server, watch_domains=["climate"])
            adapter.handle_message = AsyncMock()

            await adapter.connect()

            # Push a light event (not in watch_domains)
            await server.push_event({
                "data": {
                    "entity_id": "light.bedroom",
                    "old_state": {"state": "off", "attributes": {}},
                    "new_state": {
                        "state": "on",
                        "attributes": {"friendly_name": "Bedroom Light"},
                    },
                }
            })

            await asyncio.sleep(0.5)
            assert adapter.handle_message.call_count == 0

            await adapter.disconnect()

    @pytest.mark.asyncio
    async def test_disconnect_closes_cleanly(self):
        """disconnect() cancels listener and closes WebSocket."""
        async with FakeHAServer() as server:
            adapter = _adapter_for(server)
            await adapter.connect()
            ws_ref = adapter._ws

            await adapter.disconnect()

            assert adapter._running is False
            assert adapter._listen_task is None
            assert adapter._ws is None
            # The original WS reference should be closed
            assert ws_ref.closed


# ---------------------------------------------------------------------------
# 2. REST tool handlers (real HTTP against fake server)
# ---------------------------------------------------------------------------


class TestToolRest:
    """Call the async tool functions directly against the fake server.

    Note: we call ``_async_*`` instead of the sync ``_handle_*`` wrappers
    because the sync wrappers use ``_run_async`` which blocks the event
    loop, deadlocking with the in-process fake server.  The async functions
    are the real logic; the sync wrappers are trivial bridge code already
    covered by unit tests.
    """

    @pytest.mark.asyncio
    async def test_list_entities_returns_all(self, monkeypatch):
        """_async_list_entities returns all entities from the fake server."""
        async with FakeHAServer() as server:
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", server.token,
            )

            result = await _async_list_entities()

            assert result["count"] == len(ENTITY_STATES)
            ids = {e["entity_id"] for e in result["entities"]}
            assert "light.bedroom" in ids
            assert "climate.thermostat" in ids

    @pytest.mark.asyncio
    async def test_list_entities_domain_filter(self, monkeypatch):
        """Domain filter is applied after fetching from server."""
        async with FakeHAServer() as server:
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", server.token,
            )

            result = await _async_list_entities(domain="light")

            assert result["count"] == 2
            for e in result["entities"]:
                assert e["entity_id"].startswith("light.")

    @pytest.mark.asyncio
    async def test_get_state_single_entity(self, monkeypatch):
        """_async_get_state returns full entity details."""
        async with FakeHAServer() as server:
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", server.token,
            )

            result = await _async_get_state("light.bedroom")

            assert result["entity_id"] == "light.bedroom"
            assert result["state"] == "on"
            assert result["attributes"]["brightness"] == 200
            assert result["last_changed"] is not None

    @pytest.mark.asyncio
    async def test_get_state_not_found(self, monkeypatch):
        """Non-existent entity raises an aiohttp error (404)."""
        import aiohttp as _aiohttp

        async with FakeHAServer() as server:
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", server.token,
            )

            with pytest.raises(_aiohttp.ClientResponseError) as exc_info:
                await _async_get_state("light.nonexistent")
            assert exc_info.value.status == 404

    @pytest.mark.asyncio
    async def test_call_service_turn_on(self, monkeypatch):
        """_async_call_service sends correct payload and server records it."""
        async with FakeHAServer() as server:
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", server.token,
            )

            result = await _async_call_service(
                domain="light",
                service="turn_on",
                entity_id="light.bedroom",
                data={"brightness": 255},
            )

            assert result["success"] is True
            assert result["service"] == "light.turn_on"
            assert len(result["affected_entities"]) == 1
            assert result["affected_entities"][0]["state"] == "on"

            # Verify fake server recorded the call
            assert len(server.received_service_calls) == 1
            call = server.received_service_calls[0]
            assert call["domain"] == "light"
            assert call["service"] == "turn_on"
            assert call["data"]["entity_id"] == "light.bedroom"
            assert call["data"]["brightness"] == 255


# ---------------------------------------------------------------------------
# 3. send() -- REST notification
# ---------------------------------------------------------------------------


class TestSendNotification:
    @pytest.mark.asyncio
    async def test_send_notification_delivered(self):
        """Adapter send() delivers notification to fake server REST endpoint."""
        async with FakeHAServer() as server:
            adapter = _adapter_for(server)

            result = await adapter.send("ha_events", "Test notification from agent")

            assert result.success is True
            assert len(server.received_notifications) == 1
            notif = server.received_notifications[0]
            assert notif["title"] == "Hermes Agent"
            assert notif["message"] == "Test notification from agent"

    @pytest.mark.asyncio
    async def test_send_auth_failure(self):
        """send() returns failure when token is wrong."""
        async with FakeHAServer() as server:
            config = PlatformConfig(
                enabled=True,
                token="wrong-token",
                extra={"url": server.url},
            )
            adapter = HomeAssistantAdapter(config)

            result = await adapter.send("ha_events", "Should fail")

            assert result.success is False
            assert "401" in result.error


# ---------------------------------------------------------------------------
# 4. Auth and error cases
# ---------------------------------------------------------------------------


class TestAuthAndErrors:
    @pytest.mark.asyncio
    async def test_rest_unauthorized(self, monkeypatch):
        """Async function raises on 401 when token is wrong."""
        import aiohttp as _aiohttp

        async with FakeHAServer() as server:
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", "bad-token",
            )

            with pytest.raises(_aiohttp.ClientResponseError) as exc_info:
                await _async_list_entities()
            assert exc_info.value.status == 401

    @pytest.mark.asyncio
    async def test_rest_server_error(self, monkeypatch):
        """Async function raises on 500 response."""
        import aiohttp as _aiohttp

        async with FakeHAServer() as server:
            server.force_500 = True
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_URL", server.url,
            )
            monkeypatch.setattr(
                "tools.homeassistant_tool._HASS_TOKEN", server.token,
            )

            with pytest.raises(_aiohttp.ClientResponseError) as exc_info:
                await _async_list_entities()
            assert exc_info.value.status == 500
