"""Tests for connection module."""

import pytest

from kaleidescape.connection import Connection
from kaleidescape.const import STATE_CONNECTED, STATE_DISCONNECTED, STATE_RECONNECTING
from kaleidescape.dispatcher import Dispatcher

from . import create_signal
from .emulator import Emulator

# pylint: disable=unused-argument


@pytest.mark.asyncio
async def test_init():
    """Test init sets properties."""
    connection = Connection(Dispatcher())
    assert isinstance(connection.dispatcher, Dispatcher)
    await connection.disconnect()


@pytest.mark.asyncio
async def test_connect_fails():
    """Test connect fails when host not available."""
    connection = Connection(Dispatcher())
    with pytest.raises(ConnectionError) as err:
        await connection.connect("127.0.0.1", port=10001, timeout=1)
    assert isinstance(err.value, ConnectionRefusedError)

    # Also fails for initial connection even with reconnect.
    with pytest.raises(ConnectionError) as err:
        await connection.connect("127.0.0.1", port=10001, timeout=1, reconnect=True)
    assert isinstance(err.value, ConnectionRefusedError)


@pytest.mark.asyncio
async def test_connect_timeout():
    """Test connect timeout when host not available."""
    connection = Connection(Dispatcher())
    with pytest.raises(ConnectionError):
        await connection.connect("0.0.0.1", port=10001, timeout=1)

    # Also fails for initial connection even with reconnect.
    with pytest.raises(ConnectionError):
        await connection.connect("0.0.0.1", port=10001, timeout=1, reconnect=True)


@pytest.mark.asyncio
async def test_connect_succeeds(emulator: Emulator):
    """Test connect updates state and emits signal."""
    dispatcher = Dispatcher()
    connection = Connection(dispatcher)
    assert connection.state == STATE_DISCONNECTED
    signal = create_signal(dispatcher, STATE_CONNECTED)
    await connection.connect("127.0.0.1", port=10001, timeout=1)
    await signal.wait()
    assert connection.ip == "127.0.0.1"
    assert connection.port == 10001
    assert connection.timeout == 1
    assert connection.state == STATE_CONNECTED
    await connection.disconnect()


@pytest.mark.asyncio
async def test_manual_disconnect(emulator: Emulator, connection: Connection):
    """Test disconnect updates state and emits signal."""
    signal = create_signal(connection.dispatcher, STATE_DISCONNECTED)
    await connection.disconnect()
    await signal.wait()
    assert connection.state == STATE_DISCONNECTED
    # await emulator.stop()


@pytest.mark.asyncio
async def test_event_disconnect(emulator: Emulator, connection: Connection):
    """Test connection error during event results in disconnected."""
    assert connection.state == STATE_CONNECTED
    signal = create_signal(connection.dispatcher, STATE_DISCONNECTED)
    await emulator.stop()
    await signal.wait()
    assert connection.state == STATE_DISCONNECTED


@pytest.mark.asyncio
async def test_reconnect_during_event(emulator: Emulator):
    """Test reconnect while waiting for events/responses."""
    dispatcher = Dispatcher()
    connection = Connection(dispatcher)

    connect_signal = create_signal(dispatcher, STATE_CONNECTED)
    disconnect_signal = create_signal(dispatcher, STATE_DISCONNECTED)

    # Assert connection
    await connection.connect(
        "127.0.0.1", port=10001, timeout=1, reconnect=True, reconnect_delay=1
    )
    await connect_signal.wait()
    assert connection.state == STATE_CONNECTED
    connect_signal.clear()

    # Assert transitions to reconnecting and emits disconnect signal
    await emulator.stop()
    await disconnect_signal.wait()
    assert connection.state == STATE_RECONNECTING

    # Assert reconnects once server is back up and emits connected signal
    await emulator.start()
    await connect_signal.wait()
    assert connection.state == STATE_CONNECTED

    await connection.disconnect()


@pytest.mark.asyncio
async def test_reconnect_cancelled(emulator):
    """Test reconnect is canceled by calling disconnect."""
    dispatcher = Dispatcher()
    connection = Connection(dispatcher)

    connect_signal = create_signal(dispatcher, STATE_CONNECTED)
    disconnect_signal = create_signal(dispatcher, STATE_DISCONNECTED)

    # Assert open and fires connected
    await connection.connect(
        "127.0.0.1", port=10001, timeout=1, reconnect=True, reconnect_delay=0.5
    )
    await connect_signal.wait()
    assert connection.state == STATE_CONNECTED

    # Assert transitions to reconnecting and emits disconnect signal
    await emulator.stop()
    await disconnect_signal.wait()
    assert connection.state == STATE_RECONNECTING

    await connection.disconnect()
    assert connection.state == STATE_DISCONNECTED
