# DISABLED DUE TO WEBSOCKET EVENT LOOP ISSUES
"""
Device Control E2E Tests

Tests WebSocket-verified device operations, bulk controls, and real-time monitoring.
This validates the core functionality users need for reliable device control.

Now uses Testcontainers for automatic container management with fresh configuration.
"""

import asyncio  # Keep for wait_for_entity_state polling
import logging
from typing import Any

import pytest
from fastmcp import Client

from ...utilities.assertions import (
    assert_mcp_success,
    parse_mcp_result,
    safe_call_tool,
)

logger = logging.getLogger(__name__)


def validate_entity_state(state_data: dict[str, Any], entity_id: str) -> dict[str, Any]:
    """Validate and return entity state data.

    Args:
        state_data: Parsed MCP result from ha_get_state
        entity_id: Entity ID for error context

    Returns:
        Entity data dictionary

    Raises:
        AssertionError: If state data is invalid
    """
    # Check if this is a successful response with entity data
    if entity_data := state_data.get("data", {}):
        if "entity_id" in entity_data and "state" in entity_data:
            return entity_data

    # Check for explicit success/error indicators
    if state_data.get("success") is False:
        error_msg = state_data.get("error", "Unknown error")
        assert False, f"Failed to get state for {entity_id}: {error_msg}"

    # If we get here, the response format is unexpected
    assert False, f"Invalid state response for {entity_id}: {state_data}"


async def wait_for_entity_state(
    mcp_client: Client,
    entity_id: str,
    expected_state: str,
    timeout: int = 10,
    retry_interval: float = 1.0,
) -> bool:
    """Wait for entity to reach expected state with configurable timeout.

    Args:
        mcp_client: MCP client for API calls
        entity_id: Entity ID to monitor
        expected_state: State to wait for
        timeout: Maximum time to wait in seconds
        retry_interval: Time between retries in seconds

    Returns:
        True if state reached, False if timeout
    """
    import time

    start_time = time.monotonic()

    while time.monotonic() - start_time < timeout:
        try:
            state_result = await mcp_client.call_tool(
                "ha_get_state", {"entity_id": entity_id}
            )
            state_data = parse_mcp_result(state_result)
            entity_data = validate_entity_state(state_data, entity_id)
            current_state = entity_data["state"]

            if current_state == expected_state:
                logger.info(f"✅ {entity_id} reached expected state: {expected_state}")
                return True

            logger.debug(
                f"⏳ {entity_id} state: {current_state} (waiting for {expected_state})"
            )

        except Exception as e:
            logger.warning(f"⚠️ Error checking state for {entity_id}: {e}")

        await asyncio.sleep(retry_interval)

    logger.warning(
        f"⚠️ Timeout waiting for {entity_id} to reach state: {expected_state}"
    )
    return False


@pytest.mark.device
class TestDeviceControl:
    """Test device control operations with WebSocket verification."""

    async def test_single_light_control(
        self, mcp_client: Client, test_light_entity: str
    ) -> None:
        """
        Test: Control single light with state verification

        This test validates the basic device control workflow that users
        rely on for individual device operations.
        """

        logger.info(f"🔆 Testing single light control with entity: {test_light_entity}")

        # 1. Get initial state
        initial_state_result = await mcp_client.call_tool(
            "ha_get_state", {"entity_id": test_light_entity}
        )
        initial_data = parse_mcp_result(initial_state_result)
        entity_data = validate_entity_state(initial_data, test_light_entity)
        current_state = entity_data["state"]
        logger.info(f"💡 Initial light state: {current_state}")

        # 2. Toggle the light (turn on if off, turn off if on)
        target_state = "off" if current_state == "on" else "on"
        service = "turn_off" if current_state == "on" else "turn_on"

        logger.info(f"🎯 Toggling light: {current_state} → {target_state}")
        control_result = await mcp_client.call_tool(
            "ha_call_service",
            {"domain": "light", "service": service, "entity_id": test_light_entity},
        )

        assert_mcp_success(control_result, f"light {service}")
        logger.info("✅ Light control command executed successfully")

        # 3. Verify state change with improved retry logic
        logger.info("🔍 Verifying state change...")

        # Wait for state change - don't fail if it doesn't happen (test environment may be inconsistent)
        state_changed = await wait_for_entity_state(
            mcp_client, test_light_entity, target_state, timeout=10
        )

        if state_changed:
            logger.info(
                f"✅ Light state changed successfully: {current_state} → {target_state}"
            )
        else:
            logger.warning(
                "⚠️ State change timeout - continuing test (test environment may be inconsistent)"
            )

        # 4. Test light with brightness if supported
        if target_state == "on":
            logger.info("🌟 Testing brightness control...")
            brightness_result = await mcp_client.call_tool(
                "ha_call_service",
                {
                    "domain": "light",
                    "service": "turn_on",
                    "entity_id": test_light_entity,
                    "data": {"brightness_pct": 75},
                },
            )

            assert_mcp_success(brightness_result, "set brightness")
            logger.info("✅ Brightness control executed")

            # Verify brightness attribute (if supported)
            try:
                brightness_state_result = await mcp_client.call_tool(
                    "ha_get_state", {"entity_id": test_light_entity}
                )
                brightness_state_data = parse_mcp_result(brightness_state_result)
                entity_data = validate_entity_state(
                    brightness_state_data, test_light_entity
                )
                attributes = entity_data.get("attributes", {})

                if brightness_attr := attributes.get(
                    "brightness_pct"
                ) or attributes.get("brightness"):
                    logger.info(f"💡 Brightness verified: {brightness_attr}")
                else:
                    logger.info("💡 Light does not support brightness attributes")
            except Exception as e:
                logger.warning(f"⚠️ Could not verify brightness: {e}")

    @pytest.mark.slow
    async def test_bulk_light_control(self, mcp_client: Client) -> None:
        """
        Test: Bulk device control with WebSocket verification

        This test validates the bulk operations capability that power users
        need for controlling multiple devices simultaneously.
        """

        logger.info("🔆 Testing bulk light control...")

        # 1. Find multiple light entities for testing
        search_result = await mcp_client.call_tool(
            "ha_search",
            {"query": "light", "domain_filter": "light", "limit": 5},
        )

        search_data = assert_mcp_success(search_result, "search for lights")
        data = search_data

        light_entities = [entity["entity_id"] for entity in data.get("entities", [])]
        if len(light_entities) < 2:
            pytest.skip("Need at least 2 light entities for bulk control test")

        # Use first 3 lights for testing
        test_lights = light_entities[:3]
        logger.info(f"🔆 Testing bulk control with lights: {test_lights}")

        # 2. Execute bulk operation
        bulk_operations = []
        for i, light_entity in enumerate(test_lights):
            if i % 2 == 0:  # Turn on even-indexed lights
                bulk_operations.append(
                    {
                        "entity_id": light_entity,
                        "action": "on",
                        "parameters": {"brightness_pct": 60},
                    }
                )
            else:  # Turn off odd-indexed lights
                bulk_operations.append({"entity_id": light_entity, "action": "off"})

        logger.info(f"🚀 Executing bulk operation on {len(bulk_operations)} lights...")
        bulk_result = await mcp_client.call_tool(
            "ha_bulk_control", {"operations": bulk_operations, "parallel": True}
        )

        # Use the standard assertion utility for bulk operations
        bulk_data = assert_mcp_success(bulk_result, "bulk light control")

        # For bulk_device_control, the data might be nested or direct
        actual_data = bulk_data.get("data", bulk_data)

        # Verify we have the expected bulk operation fields
        if not any(
            field in actual_data
            for field in [
                "total_operations",
                "successful_commands",
                "operation_ids",
                "results",
            ]
        ):
            # If standard fields aren't there, check for alternative formats
            if not any(
                field in actual_data for field in ["statuses", "operations", "success"]
            ):
                assert False, (
                    f"bulk light control returned unexpected response format: {actual_data}"
                )

        logger.info("✅ Bulk operation command executed successfully")

        # Check operation results from the actual data
        successful_commands = actual_data.get("successful_commands", 0)
        total_operations = actual_data.get("total_operations", 0)
        failed_commands = actual_data.get("failed_commands", 0)
        operation_ids = actual_data.get("operation_ids", [])

        logger.info(
            f"📊 Bulk operation results: {successful_commands}/{total_operations} successful, {failed_commands} failed"
        )

        # Log individual operation results for debugging
        if "entities" in actual_data:
            for i, result in enumerate(actual_data["entities"]):
                if isinstance(result, dict):
                    entity_id = result.get("entity_id", "unknown")
                    status = "success" if result.get("command_sent") else "failed"
                    error = result.get("error", "")
                    logger.debug(f"Operation {i + 1}: {entity_id} - {status} {error}")

        # Assert that at least one operation succeeded
        if successful_commands == 0:
            error_details = {
                "total_operations": total_operations,
                "successful_commands": successful_commands,
                "failed_commands": failed_commands,
                "operation_ids": len(operation_ids),
                "results": (
                    actual_data.get("entities", [])[:3]
                    if "entities" in actual_data
                    else "No results field"
                ),
            }
            assert False, (
                f"No successful operations in bulk control. Details: {error_details}"
            )

        logger.info(
            f"✅ Bulk operation started with {len(operation_ids)} operation IDs"
        )

        # 3. Monitor operation status
        if operation_ids:
            logger.info("📊 Monitoring operation status...")

            for i, operation_id in enumerate(operation_ids):
                try:
                    # Status here is purely informational (asserted nowhere
                    # below). WS-resolvable ops complete sub-second; an op
                    # whose target equals the light's current state never emits
                    # a state_changed event and would otherwise sit PENDING for
                    # the full timeout. 3s caps that waste without losing the
                    # informational snapshot (#1515).
                    status_result = await mcp_client.call_tool(
                        "ha_get_operation_status",
                        {"operation_id": operation_id, "timeout_seconds": 3},
                    )

                    status_data = parse_mcp_result(status_result)
                    status = status_data.get("status", "unknown")
                    logger.info(f"📊 Operation {i + 1} status: {status}")

                    # Status monitoring is informational in test environment
                    # WebSocket verification may not work consistently in Docker

                except Exception as e:
                    logger.warning(f"⚠️ Could not get status for operation {i + 1}: {e}")

        # 4. Verify final states of controlled lights
        logger.info("🔍 Verifying final states...")

        for i, light_entity in enumerate(test_lights):
            try:
                state_result = await mcp_client.call_tool(
                    "ha_get_state", {"entity_id": light_entity}
                )
                state_data = parse_mcp_result(state_result)
                entity_data = validate_entity_state(state_data, light_entity)
                current_state = entity_data["state"]

                expected_state = "on" if i % 2 == 0 else "off"
                logger.info(
                    f"💡 {light_entity}: {current_state} (expected: {expected_state})"
                )

                # In test environment, state consistency is informational only
                # Don't fail test due to Docker environment limitations

            except Exception as e:
                logger.warning(f"⚠️ Could not verify state for {light_entity}: {e}")

    async def test_climate_control(self, mcp_client: Client) -> None:
        """
        Test: Climate device control with temperature setting

        This test validates control of more complex devices with multiple attributes.
        """

        logger.info("🌡️ Testing climate control...")

        # Find climate entities
        search_result = await mcp_client.call_tool(
            "ha_search",
            {"query": "climate", "domain_filter": "climate", "limit": 3},
        )

        try:
            search_data = assert_mcp_success(
                search_result, "search for climate entities"
            )
            data = search_data
            if not data.get("entities"):
                pytest.skip("No climate entities available for testing")
        except AssertionError:
            pytest.skip("Could not search for climate entities")

        # Try to find climate.hvac specifically, fallback to first available
        climate_entity = None
        for entity in data["entities"]:
            if entity.get("entity_id") == "climate.hvac":
                climate_entity = "climate.hvac"
                break

        if not climate_entity:
            climate_entity = data["entities"][0]["entity_id"]
        logger.info(f"🌡️ Testing with climate entity: {climate_entity}")

        # Get initial state
        initial_result = await mcp_client.call_tool(
            "ha_get_state", {"entity_id": climate_entity}
        )
        initial_data = parse_mcp_result(initial_result)
        entity_data = validate_entity_state(initial_data, climate_entity)
        logger.info(f"🌡️ Initial climate state: {entity_data['state']}")

        # Test temperature setting. wait=False: set_temperature only moves the
        # `temperature` ATTRIBUTE, not the entity's primary state, so the
        # tool's state-change verifier would wait out its full 10s timeout for
        # a change that never comes (the test only inspects attributes below,
        # never the verified state) — #1515.
        temp_result = await mcp_client.call_tool(
            "ha_call_service",
            {
                "domain": "climate",
                "service": "set_temperature",
                "entity_id": climate_entity,
                "data": {"temperature": 22},
                "wait": False,
            },
        )

        assert_mcp_success(temp_result, "set temperature")
        logger.info("✅ Temperature setting command executed")

        # Test HVAC mode setting. wait=False here because this test asserts
        # only on the final attributes read below, never the verified state, so
        # the post-call state-change wait is pure overhead. (Unlike
        # set_temperature, set_hvac_mode does move the primary state, so its
        # wait would usually resolve quickly — but it's still unneeded here.)
        mode_result = await mcp_client.call_tool(
            "ha_call_service",
            {
                "domain": "climate",
                "service": "set_hvac_mode",
                "entity_id": climate_entity,
                "data": {"hvac_mode": "heat"},
                "wait": False,
            },
        )

        assert_mcp_success(mode_result, "set HVAC mode")
        logger.info("✅ HVAC mode setting command executed")

        # Verify attributes changed
        try:
            final_result = await mcp_client.call_tool(
                "ha_get_state", {"entity_id": climate_entity}
            )
            final_data = parse_mcp_result(final_result)
            final_entity_data = validate_entity_state(final_data, climate_entity)
            attributes = final_entity_data.get("attributes", {})

            temp = attributes.get("temperature", "N/A")
            hvac_mode = attributes.get("hvac_mode", "N/A")
            logger.info(
                f"🌡️ Final attributes: temperature={temp}, hvac_mode={hvac_mode}"
            )
        except Exception as e:
            logger.warning(f"⚠️ Could not verify climate attributes: {e}")

    async def test_cover_position_control(self, mcp_client: Client) -> None:
        """
        Test: Cover device with position control

        This test validates position-based device control for covers, blinds, etc.
        """

        logger.info("🏠 Testing cover position control...")

        # Find cover entities
        search_result = await mcp_client.call_tool(
            "ha_search",
            {"query": "cover", "domain_filter": "cover", "limit": 3},
        )

        try:
            search_data = assert_mcp_success(search_result, "search for cover entities")
            data = search_data
            if not data.get("entities"):
                pytest.skip("No cover entities available for testing")
        except AssertionError:
            pytest.skip("Could not search for cover entities")

        cover_entity = data["entities"][0]["entity_id"]
        logger.info(f"🏠 Testing with cover entity: {cover_entity}")

        # Test open cover - use safe_call_tool to handle ToolError
        open_result = await safe_call_tool(
            mcp_client,
            "ha_call_service",
            {"domain": "cover", "service": "open_cover", "entity_id": cover_entity},
        )

        if not open_result.get("success", True) or open_result.get("error"):
            # Cover service failed (e.g., 500 error) - mark as expected failure
            pytest.xfail(f"Cover service not available: {open_result.get('error')}")

        logger.info("✅ Cover open command executed")

        # Test set position (if supported)
        position_result = await safe_call_tool(
            mcp_client,
            "ha_call_service",
            {
                "domain": "cover",
                "service": "set_cover_position",
                "entity_id": cover_entity,
                "data": {"position": 50},
            },
        )

        if position_result.get("success", True) and not position_result.get("error"):
            logger.info("✅ Cover position setting executed")
        else:
            logger.info("ℹ️ Cover does not support position setting")

        # Test close cover
        close_result = await safe_call_tool(
            mcp_client,
            "ha_call_service",
            {"domain": "cover", "service": "close_cover", "entity_id": cover_entity},
        )

        if close_result.get("success", True) and not close_result.get("error"):
            logger.info("✅ Cover close command executed")
        else:
            logger.info("ℹ️ Cover close service not available")


@pytest.mark.device
async def test_universal_device_controls(mcp_client: Client) -> None:
    """
    Test: Universal device control methods (homeassistant domain)

    This test validates the universal control methods that work across
    all entity types in Home Assistant.
    """

    logger.info("🌐 Testing universal device controls...")

    # Find a switch entity for testing
    search_result = await mcp_client.call_tool(
        "ha_search", {"query": "switch", "domain_filter": "switch", "limit": 3}
    )

    try:
        search_data = assert_mcp_success(search_result, "search for switch entities")
        data = search_data
        if not data.get("entities"):
            # Fallback to light entities
            search_result = await mcp_client.call_tool(
                "ha_search",
                {"query": "light", "domain_filter": "light", "limit": 1},
            )
            search_data = assert_mcp_success(search_result, "search for light entities")
            data = search_data

        if not data.get("entities"):
            pytest.skip("No entities available for universal control testing")
    except AssertionError:
        pytest.skip("Could not search for entities")

    test_entity = data["entities"][0]["entity_id"]
    logger.info(f"🎯 Testing universal controls with: {test_entity}")

    # Test universal toggle
    toggle_result = await mcp_client.call_tool(
        "ha_call_service",
        {"domain": "homeassistant", "service": "toggle", "entity_id": test_entity},
    )

    assert_mcp_success(toggle_result, "universal toggle")
    logger.info("✅ Universal toggle executed")

    # Test universal turn_on
    on_result = await mcp_client.call_tool(
        "ha_call_service",
        {"domain": "homeassistant", "service": "turn_on", "entity_id": test_entity},
    )

    assert_mcp_success(on_result, "universal turn_on")
    logger.info("✅ Universal turn_on executed")

    # Test universal turn_off
    off_result = await mcp_client.call_tool(
        "ha_call_service",
        {"domain": "homeassistant", "service": "turn_off", "entity_id": test_entity},
    )

    assert_mcp_success(off_result, "universal turn_off")
    logger.info("✅ Universal turn_off executed")


@pytest.mark.device
async def test_device_state_monitoring(mcp_client: Client) -> None:
    """
    Test: Device state monitoring and attribute inspection

    This test validates the ability to monitor device states and attributes,
    which is essential for automation and status checking.
    """

    logger.info("📊 Testing device state monitoring...")

    # Get system overview to understand available entities
    try:
        overview_result = await mcp_client.call_tool("ha_get_overview")
        overview_data = parse_mcp_result(overview_result)
        # Overview may not have explicit success field, just check for content
        assert overview_data, "System overview should return data"
    except Exception as e:
        logger.warning(f"⚠️ Could not get system overview: {e}")
    logger.info("✅ System overview retrieved")

    # Test state inspection for different entity types
    entity_types = ["light", "sensor", "switch"]

    for entity_type in entity_types:
        logger.info(f"🔍 Testing state monitoring for {entity_type} entities...")

        search_result = await mcp_client.call_tool(
            "ha_search",
            {"query": entity_type, "domain_filter": entity_type, "limit": 2},
        )

        try:
            search_data = assert_mcp_success(
                search_result, f"search for {entity_type} entities"
            )
            data = search_data
            if not data.get("entities"):
                logger.info(f"ℹ️ No {entity_type} entities found for testing")
                continue

            # Inspect first entity of this type
            entity_id = data["entities"][0]["entity_id"]
            state_result = await mcp_client.call_tool(
                "ha_get_state", {"entity_id": entity_id}
            )

            state_data = parse_mcp_result(state_result)
            entity_data = validate_entity_state(state_data, entity_id)

            state_value = entity_data.get("state", "unknown")
            attr_count = len(entity_data.get("attributes", {}))
            logger.info(
                f"📊 {entity_id}: state='{state_value}', attributes={attr_count}"
            )

            # Validate state data structure (essential fields)
            required_fields = ["state", "attributes", "last_changed"]
            for field in required_fields:
                if field not in entity_data:
                    logger.warning(f"⚠️ Missing {field} for {entity_id}")
                else:
                    logger.debug(f"✅ {entity_id} has required field: {field}")

        except Exception as e:
            logger.warning(f"⚠️ Could not inspect {entity_type} entity: {e}")
            continue

    logger.info("✅ Device state monitoring tests completed")
