"""Common stuff for Fritz!Tools tests."""

from collections.abc import Generator
from copy import deepcopy
import logging
from typing import Any
from unittest.mock import MagicMock, patch

from fritzconnection.lib.fritzhosts import FritzHosts
from fritzconnection.lib.fritzstatus import FritzStatus
import pytest

from homeassistant.components.fritz.coordinator import FritzConnectionCached

from .const import (
    MOCK_FB_SERVICES,
    MOCK_HOST_ATTRIBUTES_DATA,
    MOCK_MESH_DATA,
    MOCK_MODELNAME,
    MOCK_STATUS_CONNECTION_DATA,
)

LOGGER = logging.getLogger(__name__)


class FritzServiceMock:
    """Service mocking."""

    def __init__(self, actions: list[str]) -> None:
        """Init Service mock."""
        self.actions = actions


class FritzConnectionMock:
    """FritzConnection mocking."""

    def __init__(self, fc_data: dict[str, dict[str, Any]]) -> None:
        """Init Mocking class."""
        self._fc_data: dict[str, dict[str, Any]]
        self.services: dict[str, FritzServiceMock]

        self._call_cache: dict[str, dict[str, Any]] = {}
        self.modelname = MOCK_MODELNAME
        self._side_effect: Exception | None = None

        self._service_normalization(fc_data)

        LOGGER.debug("-" * 80)
        LOGGER.debug("FritzConnectionMock - services: %s", self.services)

    def _service_normalization(self, fc_data: dict[str, dict[str, Any]]) -> None:
        """Normalize service name."""
        self._fc_data = deepcopy(fc_data)
        self.services = {
            service.replace(":", ""): FritzServiceMock(list(actions.keys()))
            for service, actions in fc_data.items()
        }

    def call_action_side_effect(self, side_effect: Exception | None) -> None:
        """Set or unset a side_effect for call_action."""
        self._side_effect = side_effect

    def override_services(self, fc_data: dict[str, dict[str, Any]]) -> None:
        """Override services data."""
        self._service_normalization(fc_data)

    def clear_cache(self) -> None:
        """Mock clear_cache method."""
        return FritzConnectionCached.clear_cache(self)

    def call_action(self, service: str, action: str, **kwargs: Any) -> Any:
        """Simulate TR-064 call with service name normalization."""
        LOGGER.debug(
            "_call_action service: %s, action: %s, **kwargs: %s",
            service,
            action,
            {**kwargs},
        )
        if self._side_effect:
            raise self._side_effect

        normalized = service
        if service not in self._fc_data:
            # tolerate DeviceInfo1 <-> DeviceInfo:1 and similar
            if (
                (":" in service and (alt := service.replace(":", "")) in self._fc_data)
                or (alt := f"{service}1") in self._fc_data
                or (alt := f"{service}:1") in self._fc_data
                or (
                    service.endswith("1")
                    and ":" not in service
                    and (alt := f"{service[:-1]}:1") in self._fc_data
                )
            ):
                normalized = alt

        action_data = self._fc_data.get(normalized, {}).get(action, {})
        if kwargs:
            if (index := kwargs.get("NewIndex")) is None:
                index = next(iter(kwargs.values()))
            if isinstance(action_data, dict) and index in action_data:
                return action_data[index]

        return action_data


def wifi_services_with_ssids(ssid_1: str, ssid_2: str) -> dict[str, dict[str, Any]]:
    """Return Fritz services with overridden Wi-Fi SSIDs."""
    services = deepcopy(MOCK_FB_SERVICES)
    for index, ssid in enumerate((ssid_1, ssid_2), start=1):
        services[f"WLANConfiguration{index}"]["GetInfo"]["NewSSID"] = ssid
        services[f"WLANConfiguration{index}"]["GetSSID"]["NewSSID"] = ssid
    return services


@pytest.fixture(name="fc_data")
def fc_data_mock() -> dict[str, dict[str, Any]]:
    """Fixture for default fc_data."""
    return deepcopy(MOCK_FB_SERVICES)


@pytest.fixture
def fc_class_mock(fc_data: dict[str, dict[str, Any]]) -> Generator[MagicMock]:
    """Fixture that sets up a mocked FritzConnection class."""
    with patch(
        "homeassistant.components.fritz.coordinator.FritzConnectionCached",
        autospec=True,
    ) as result:
        result.return_value = FritzConnectionMock(fc_data)
        yield result


@pytest.fixture
def fh_class_mock() -> Generator[type[FritzHosts]]:
    """Fixture that sets up a mocked FritzHosts class."""
    with (
        patch(
            "homeassistant.components.fritz.coordinator.FritzHosts",
            new=FritzHosts,
        ) as result,
        patch.object(
            FritzHosts,
            "get_mesh_topology",
            MagicMock(return_value=MOCK_MESH_DATA),
        ),
        patch.object(
            FritzHosts,
            "get_hosts_attributes",
            MagicMock(return_value=MOCK_HOST_ATTRIBUTES_DATA),
        ),
    ):
        yield result


@pytest.fixture
def fs_class_mock() -> Generator[type[FritzStatus]]:
    """Fixture that sets up a mocked FritzStatus class."""
    with (
        patch(
            "homeassistant.components.fritz.coordinator.FritzStatus",
            new=FritzStatus,
        ) as result,
        patch.object(
            FritzStatus,
            "get_default_connection_service",
            MagicMock(return_value=MOCK_STATUS_CONNECTION_DATA),
        ),
        patch.object(FritzStatus, "get_monitor_data", MagicMock(return_value={})),
        patch.object(
            FritzStatus, "get_cpu_temperatures", MagicMock(return_value=[42, 38])
        ),
        patch.object(FritzStatus, "has_wan_enabled", True),
    ):
        yield result
