"""Tests for WLED client."""

import pytest
import respx
from httpx import Response

from wled_controller.core.devices.wled_client import WLEDClient, WLEDInfo


@pytest.fixture
def wled_url():
    """Provide test WLED device URL."""
    return "http://192.168.1.100"


@pytest.fixture
def mock_wled_info():
    """Provide mock WLED info response."""
    return {
        "name": "Test WLED",
        "ver": "0.14.0",
        "leds": {"count": 150},
        "brand": "WLED",
        "product": "FOSS",
        "mac": "AA:BB:CC:DD:EE:FF",
        "ip": "192.168.1.100",
    }


@pytest.fixture
def mock_wled_cfg():
    """Provide mock WLED config response (needed by get_info)."""
    return {
        "hw": {
            "led": {
                "ins": [
                    {"start": 0, "len": 150, "order": 1, "pin": [2], "type": 22}
                ]
            }
        }
    }


@pytest.fixture
def mock_wled_state():
    """Provide mock WLED state response."""
    return {
        "on": True,
        "bri": 255,
        "seg": [{"id": 0, "on": True}],
    }


def _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Set up respx mocks for all endpoints hit during connect().

    connect() calls get_info() which hits /json/info + /json/cfg,
    then snapshot_device_state() which hits /json/state.
    """
    respx.get(f"{wled_url}/json/info").mock(
        return_value=Response(200, json=mock_wled_info)
    )
    respx.get(f"{wled_url}/json/cfg").mock(
        return_value=Response(200, json=mock_wled_cfg)
    )
    respx.get(f"{wled_url}/json/state").mock(
        return_value=Response(200, json=mock_wled_state)
    )


@pytest.mark.asyncio
@respx.mock
async def test_wled_client_connect(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test connecting to WLED device."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)

    client = WLEDClient(wled_url)
    success = await client.connect()

    assert success is True
    assert client.is_connected is True

    await client.close()


@pytest.mark.asyncio
@respx.mock
async def test_wled_client_connect_failure(wled_url):
    """Test connection failure handling."""
    respx.get(f"{wled_url}/json/info").mock(
        return_value=Response(500, text="Internal Server Error")
    )

    client = WLEDClient(wled_url, retry_attempts=1)

    with pytest.raises(RuntimeError):
        await client.connect()

    assert client.is_connected is False


@pytest.mark.asyncio
@respx.mock
async def test_get_info(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test getting device info."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)

    async with WLEDClient(wled_url) as client:
        info = await client.get_info()

        assert isinstance(info, WLEDInfo)
        assert info.name == "Test WLED"
        assert info.version == "0.14.0"
        assert info.led_count == 150
        assert info.mac == "AA:BB:CC:DD:EE:FF"


@pytest.mark.asyncio
@respx.mock
async def test_get_state(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test getting device state."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)

    async with WLEDClient(wled_url) as client:
        state = await client.get_state()

        assert state["on"] is True
        assert state["bri"] == 255


@pytest.mark.asyncio
@respx.mock
async def test_send_pixels(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test sending pixel data."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)
    respx.post(f"{wled_url}/json/state").mock(
        return_value=Response(200, json={"success": True})
    )

    async with WLEDClient(wled_url) as client:
        pixels = [
            (255, 0, 0),  # Red
            (0, 255, 0),  # Green
            (0, 0, 255),  # Blue
        ]

        success = await client.send_pixels(pixels, brightness=200)
        assert success is True


@pytest.mark.asyncio
@respx.mock
async def test_send_pixels_invalid_values(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test sending invalid pixel values."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)

    async with WLEDClient(wled_url) as client:
        # Invalid RGB value
        with pytest.raises(ValueError):
            await client.send_pixels([(300, 0, 0)])

        # Invalid brightness
        with pytest.raises(ValueError):
            await client.send_pixels([(255, 0, 0)], brightness=300)

        # Empty pixels list
        with pytest.raises(ValueError):
            await client.send_pixels([])


@pytest.mark.asyncio
@respx.mock
async def test_set_power(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test turning device on/off."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)
    respx.post(f"{wled_url}/json/state").mock(
        return_value=Response(200, json={"success": True})
    )

    async with WLEDClient(wled_url) as client:
        # Turn on
        success = await client.set_power(True)
        assert success is True

        # Turn off
        success = await client.set_power(False)
        assert success is True


@pytest.mark.asyncio
@respx.mock
async def test_set_brightness(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test setting brightness."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)
    respx.post(f"{wled_url}/json/state").mock(
        return_value=Response(200, json={"success": True})
    )

    async with WLEDClient(wled_url) as client:
        success = await client.set_brightness(128)
        assert success is True

        # Invalid brightness
        with pytest.raises(ValueError):
            await client.set_brightness(300)


@pytest.mark.asyncio
@respx.mock
async def test_test_connection(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test connection testing."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)

    async with WLEDClient(wled_url) as client:
        success = await client.test_connection()
        assert success is True


@pytest.mark.asyncio
@respx.mock
async def test_retry_logic(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test retry logic on failures."""
    # Mock to fail twice, then succeed on /json/info
    info_call_count = 0

    def mock_info_response(request):
        nonlocal info_call_count
        info_call_count += 1
        if info_call_count < 3:
            return Response(500, text="Error")
        return Response(200, json=mock_wled_info)

    respx.get(f"{wled_url}/json/info").mock(side_effect=mock_info_response)
    respx.get(f"{wled_url}/json/cfg").mock(
        return_value=Response(200, json=mock_wled_cfg)
    )
    respx.get(f"{wled_url}/json/state").mock(
        return_value=Response(200, json=mock_wled_state)
    )

    client = WLEDClient(wled_url, retry_attempts=3, retry_delay=0.1)
    success = await client.connect()

    assert success is True
    assert info_call_count == 3  # Failed 2 times, succeeded on 3rd

    await client.close()


@pytest.mark.asyncio
@respx.mock
async def test_context_manager(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
    """Test async context manager usage."""
    _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)

    async with WLEDClient(wled_url) as client:
        assert client.is_connected is True

    # Client should be closed after context
    assert client.is_connected is False


@pytest.mark.asyncio
async def test_request_without_connection(wled_url):
    """Test making request without connecting first."""
    client = WLEDClient(wled_url)

    with pytest.raises(RuntimeError):
        await client.get_state()
