import socket
from unittest.mock import MagicMock, patch

import pytest

from hamqtt.config import MqttConfig
from hamqtt.mqtt_client import MqttClient


@pytest.fixture  # type: ignore[misc]
def mock_mqtt_config() -> MqttConfig:
    return MqttConfig(host="localhost", port=1883, username="user", password="password")


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_mqtt_client_initialization(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test initializing MqttClient sets up the underlying client correctly"""
    client = MqttClient(mock_mqtt_config)

    mock_mqtt_cls.assert_called_once()
    client.client.username_pw_set.assert_called_with("user", "password")


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_mqtt_connect(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test connecting to the broker"""
    client = MqttClient(mock_mqtt_config)
    client.connect()

    client.client.connect.assert_called_with("localhost", 1883, 60)
    client.client.loop_start.assert_called_once()


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_mqtt_connect_failure(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test connection failure raises exception"""
    client = MqttClient(mock_mqtt_config)
    client.client.connect.side_effect = socket.error("Connection refused")

    with pytest.raises(socket.error):
        client.connect()


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_mqtt_disconnect(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test disconnecting stops the loop"""
    client = MqttClient(mock_mqtt_config)
    client.disconnect()

    client.client.loop_stop.assert_called_once()
    client.client.disconnect.assert_called_once()


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_mqtt_publish(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test publishing message"""
    client = MqttClient(mock_mqtt_config)

    # Mocking the publish return value to simulate wait_for_publish
    mock_info = MagicMock()
    client.client.publish.return_value = mock_info

    client.publish("test/topic", "payload", retain=True)

    client.client.publish.assert_called_with("test/topic", "payload", retain=True)
    mock_info.wait_for_publish.assert_called_once()


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_mqtt_publish_json_serialization(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test publishing non-string payload serializes to JSON"""
    client = MqttClient(mock_mqtt_config)

    payload = {"key": "value"}
    client.publish("test/topic", payload)

    # Check that payload was serialized
    client.client.publish.assert_called_with("test/topic", '{"key": "value"}', retain=False)


@patch("hamqtt.mqtt_client.mqtt.Client")
def test_context_manager(mock_mqtt_cls: MagicMock, mock_mqtt_config: MqttConfig) -> None:
    """Test MqttClient as a context manager"""
    with MqttClient(mock_mqtt_config) as client:
        assert isinstance(client, MqttClient)
        # Verify connect called
        client.client.connect.assert_called()

    # Verify disconnect called on exit
    client.client.disconnect.assert_called()
