"""Common fixtures for the Ecovacs tests."""

from collections.abc import AsyncGenerator, Generator
import logging
from typing import Any
from unittest.mock import AsyncMock, Mock, patch

from deebot_client import const
from deebot_client.command import DeviceCommandResult
from deebot_client.device import Device
from deebot_client.exceptions import ApiError
from deebot_client.models import Credentials
import pytest
from sucks import EventEmitter

from homeassistant.components.ecovacs import PLATFORMS
from homeassistant.components.ecovacs.const import DOMAIN
from homeassistant.components.ecovacs.controller import EcovacsController
from homeassistant.const import CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant

from .const import CLOUD_DEVICE_ID, STORED_ENTRY_DATA_CLOUD

from tests.common import MockConfigEntry, load_json_object_fixture


@pytest.fixture
def mock_device_id() -> Generator[None]:
    """Return a deterministic cloud device ID."""
    with patch(
        "homeassistant.components.ecovacs.util.random.choice",
        return_value=CLOUD_DEVICE_ID[0],
    ):
        yield


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
    """Override async_setup_entry."""
    with patch(
        "homeassistant.components.ecovacs.async_setup_entry", return_value=True
    ) as async_setup_entry:
        yield async_setup_entry


@pytest.fixture
def mock_config_entry_data() -> dict[str, Any]:
    """Return the default mocked config entry data."""
    return STORED_ENTRY_DATA_CLOUD


@pytest.fixture
def mock_config_entry(mock_config_entry_data: dict[str, Any]) -> MockConfigEntry:
    """Return the default mocked config entry."""
    return MockConfigEntry(
        title=mock_config_entry_data[CONF_USERNAME],
        domain=DOMAIN,
        data=mock_config_entry_data,
        minor_version=2,
    )


@pytest.fixture
def device_fixture() -> str:
    """Device class, which should be returned by the get_devices api call."""
    return "yna5x1"


@pytest.fixture
def mock_authenticator(device_fixture: str) -> Generator[Mock]:
    """Mock the authenticator."""
    with (
        patch(
            "homeassistant.components.ecovacs.controller.Authenticator",
            autospec=True,
        ) as mock,
        patch(
            "homeassistant.components.ecovacs.config_flow.Authenticator",
            new=mock,
        ),
    ):
        authenticator = mock.return_value
        authenticator.authenticate.return_value = Credentials("token", "user_id", 0)

        devices = [
            load_json_object_fixture(f"devices/{device_fixture}/device.json", DOMAIN)
        ]

        async def post_authenticated(
            path: str,
            json: dict[str, Any],
            *,
            query_params: dict[str, Any] | None = None,
            headers: dict[str, Any] | None = None,
        ) -> dict[str, Any]:
            match path:
                case const.PATH_API_APPSVR_APP:
                    return {"code": 0, "devices": devices, "errno": "0"}
                case const.PATH_API_USERS_USER:
                    return {"todo": "result", "result": "ok", "devices": devices}
                case _:
                    raise ApiError("Path not mocked: {path}")

        authenticator.post_authenticated.side_effect = post_authenticated
        yield authenticator


@pytest.fixture
def mock_authenticator_authenticate(mock_authenticator: Mock) -> AsyncMock:
    """Mock authenticator.authenticate."""
    return mock_authenticator.authenticate


@pytest.fixture
def mock_mqtt_client(mock_authenticator: Mock) -> Generator[Mock]:
    """Mock the MQTT client."""
    with (
        patch(
            "homeassistant.components.ecovacs.controller.MqttClient",
            autospec=True,
        ) as mock,
        patch(
            "homeassistant.components.ecovacs.config_flow.MqttClient",
            new=mock,
        ),
    ):
        client = mock.return_value
        client._authenticator = mock_authenticator
        client.subscribe.return_value = lambda: None
        yield client


@pytest.fixture
def mock_vacbot(device_fixture: str) -> Generator[Mock]:
    """Mock the legacy VacBot."""
    with patch(
        "homeassistant.components.ecovacs.controller.VacBot",
        autospec=True,
    ) as mock:
        vacbot = mock.return_value
        vacbot.vacuum = load_json_object_fixture(
            f"devices/{device_fixture}/device.json", DOMAIN
        )
        vacbot.statusEvents = EventEmitter()
        vacbot.batteryEvents = EventEmitter()
        vacbot.lifespanEvents = EventEmitter()
        vacbot.errorEvents = EventEmitter()
        vacbot.battery_status = None
        vacbot.charge_status = None
        vacbot.fan_speed = None
        vacbot.components = {}
        yield vacbot


@pytest.fixture
def mock_device_execute() -> Generator[AsyncMock]:
    """Mock the device execute function."""
    with patch.object(
        Device,
        "_execute_command",
        return_value=DeviceCommandResult(device_reached=True),
    ) as mock_device_execute:
        yield mock_device_execute


@pytest.fixture
def platforms() -> Platform | list[Platform]:
    """Platforms, which should be loaded during the test."""
    return PLATFORMS


@pytest.fixture
async def init_integration(
    hass: HomeAssistant,
    caplog: pytest.LogCaptureFixture,
    mock_config_entry: MockConfigEntry,
    mock_authenticator: Mock,
    mock_mqtt_client: Mock,
    mock_device_execute: AsyncMock,
    platforms: Platform | list[Platform],
) -> AsyncGenerator[MockConfigEntry]:
    """Set up the Ecovacs integration for testing."""
    # Workaround for https://github.com/home-assistant/core/issues/155417
    caplog.clear()

    if not isinstance(platforms, list):
        platforms = [platforms]

    with patch(
        "homeassistant.components.ecovacs.PLATFORMS",
        platforms,
    ):
        mock_config_entry.add_to_hass(hass)

        await hass.config_entries.async_setup(mock_config_entry.entry_id)
        await hass.async_block_till_done(wait_background_tasks=True)

        # No errors should be logged during setup
        assert not [t for t in caplog.record_tuples if t[1] >= logging.ERROR], (
            "Errors during integration setup"
        )

        yield mock_config_entry

        # Properly unload the integration to trigger cleanup
        await hass.config_entries.async_unload(mock_config_entry.entry_id)
        await hass.async_block_till_done(wait_background_tasks=True)


@pytest.fixture
def controller(init_integration: MockConfigEntry) -> EcovacsController:
    """Get the controller for the config entry."""
    return init_integration.runtime_data
