"""
Test the Loxone MCP server functionality.

Following TDD practices with pytest and async testing.
"""

import pytest
from unittest.mock import AsyncMock, patch
from fastmcp import Client
from pathlib import Path


@pytest.fixture
def server_script():
    """Path to the MCP server script."""
    return Path("src/loxone_mcp/simple_server.py")


@pytest.fixture
def mock_credentials():
    """Mock Loxone credentials for testing."""
    return {"host": "192.168.1.100", "username": "admin", "password": "test-password", "port": 80}


@pytest.mark.asyncio
async def test_mcp_server_tools_available(server_script):
    """Test that MCP server exposes the expected tools."""
    async with Client(str(server_script)) as client:
        tools = await client.list_tools()

        expected_tools = {
            "loxone_list_devices",
            "loxone_get_device_state",
            "loxone_test_connection",
        }

        available_tools = {tool.name for tool in tools}
        assert expected_tools.issubset(available_tools)


@pytest.mark.asyncio
async def test_test_connection_missing_params(server_script):
    """Test connection test with missing parameters."""
    async with Client(str(server_script)) as client:
        result = await client.call_tool("loxone_test_connection", {})

        assert not result.data["success"]
        assert "MISSING_PARAMETERS" in result.data["error_code"]


@pytest.mark.asyncio
async def test_list_devices_missing_params(server_script):
    """Test device listing with missing parameters."""
    async with Client(str(server_script)) as client:
        result = await client.call_tool("loxone_list_devices", {})

        assert not result.data["success"]
        assert "MISSING_PARAMETERS" in result.data["error_code"]


@pytest.mark.asyncio
async def test_get_device_state_invalid_uuid(server_script, mock_credentials):
    """Test getting device state with invalid UUID."""
    async with Client(str(server_script)) as client:
        result = await client.call_tool("loxone_get_device_state", {**mock_credentials, "uuid": ""})

        assert not result.data["success"]
        assert "INVALID_PARAMETER" in result.data["error_code"]


@pytest.mark.asyncio
@patch("loxone_mcp.loxone_client.LoxoneClient.connect")
async def test_connection_failure_handling(mock_connect, server_script, mock_credentials):
    """Test handling of connection failures."""
    # Mock connection failure
    mock_connect.return_value = False

    async with Client(str(server_script)) as client:
        result = await client.call_tool("loxone_test_connection", mock_credentials)

        assert not result.data["success"]
        assert "CONNECTION_FAILED" in result.data["error_code"]


@pytest.mark.asyncio
async def test_successful_connection_mock(server_script, mock_credentials):
    """Test successful connection and structure retrieval with mocking."""
    with patch("src.loxone_mcp.simple_server.LoxoneClient") as mock_client_class:
        # Setup mock client instance
        mock_client = AsyncMock()
        mock_client_class.return_value = mock_client
        mock_client.connect.return_value = True
        mock_client.get_structure.return_value = {
            "rooms": {"room1": {}, "room2": {}},
            "controls": {"ctrl1": {}, "ctrl2": {}, "ctrl3": {}},
            "categories": {"cat1": {}},
        }
        mock_client.disconnect.return_value = None

        async with Client(str(server_script)) as client:
            result = await client.call_tool("loxone_test_connection", mock_credentials)

            assert result.data["success"]
            assert result.data["host"] == mock_credentials["host"]
            assert result.data["port"] == mock_credentials["port"]

            structure_info = result.data["structure_info"]
            assert structure_info["rooms"] == 2
            assert structure_info["controls"] == 3
            assert structure_info["categories"] == 1


@pytest.mark.asyncio
@patch("loxone_mcp.loxone_client.LoxoneClient.connect")
@patch("loxone_mcp.device_manager.DeviceManager.list_devices")
@patch("loxone_mcp.loxone_client.LoxoneClient.get_structure")
@patch("loxone_mcp.loxone_client.LoxoneClient.disconnect")
async def test_list_devices_success(
    mock_disconnect,
    mock_get_structure,
    mock_list_devices,
    mock_connect,
    server_script,
    mock_credentials,
):
    """Test successful device listing."""
    # Mock successful connection and device listing
    mock_connect.return_value = True
    mock_get_structure.return_value = {}
    mock_disconnect.return_value = None

    # Mock device objects
    mock_device1 = AsyncMock()
    mock_device1.uuid = "device-1"
    mock_device1.name = "Living Room Light"
    mock_device1.type = "Switch"
    mock_device1.room = "Living Room"
    mock_device1.category = "Lighting"

    mock_device2 = AsyncMock()
    mock_device2.uuid = "device-2"
    mock_device2.name = "Kitchen Dimmer"
    mock_device2.type = "Dimmer"
    mock_device2.room = "Kitchen"
    mock_device2.category = "Lighting"

    mock_list_devices.return_value = [mock_device1, mock_device2]

    async with Client(str(server_script)) as client:
        result = await client.call_tool("loxone_list_devices", mock_credentials)

        assert result.data["success"]
        assert result.data["count"] == 2

        devices = result.data["devices"]
        assert len(devices) == 2
        assert devices[0]["name"] == "Living Room Light"
        assert devices[1]["name"] == "Kitchen Dimmer"


@pytest.mark.asyncio
async def test_parameter_validation():
    """Test that tool parameters are properly validated."""
    # This test ensures our tools validate input parameters correctly
    # without needing to actually connect to a Loxone server

    from loxone_mcp.config import LoxoneConfig

    # Test missing required environment variables
    with pytest.raises(ValueError, match="Missing required environment variables"):
        LoxoneConfig.from_env()

    # Test valid configuration
    config = LoxoneConfig(host="192.168.1.100", username="admin", password="password", port=80)

    assert config.host == "192.168.1.100"
    assert config.username == "admin"
    assert config.password == "password"
    assert config.port == 80
