"""Common fixtures for the Gardena Bluetooth tests."""
from collections.abc import Awaitable, Callable, Generator
from typing import Any
from unittest.mock import AsyncMock, Mock, patch

from freezegun import freeze_time
from gardena_bluetooth.client import Client
from gardena_bluetooth.const import DeviceInformation
from gardena_bluetooth.exceptions import CharacteristicNotFound
from gardena_bluetooth.parse import Characteristic
import pytest

from homeassistant.components.gardena_bluetooth.const import DOMAIN
from homeassistant.components.gardena_bluetooth.coordinator import SCAN_INTERVAL
from homeassistant.const import CONF_ADDRESS
from homeassistant.core import HomeAssistant

from . import WATER_TIMER_SERVICE_INFO

from tests.common import MockConfigEntry, async_fire_time_changed


@pytest.fixture
def mock_entry():
    """Create hass config fixture."""
    return MockConfigEntry(
        domain=DOMAIN, data={CONF_ADDRESS: WATER_TIMER_SERVICE_INFO.address}
    )


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


@pytest.fixture
def mock_read_char_raw():
    """Mock data on device."""
    return {
        DeviceInformation.firmware_version.uuid: b"1.2.3",
        DeviceInformation.model_number.uuid: b"Mock Model",
    }


@pytest.fixture
async def scan_step(
    hass: HomeAssistant,
) -> Generator[None, None, Callable[[], Awaitable[None]]]:
    """Step system time forward."""

    with freeze_time("2023-01-01", tz_offset=1) as frozen_time:

        async def delay():
            """Trigger delay in system."""
            frozen_time.tick(delta=SCAN_INTERVAL)
            async_fire_time_changed(hass)
            await hass.async_block_till_done()

        yield delay


@pytest.fixture(autouse=True)
def mock_client(
    enable_bluetooth: None, scan_step, mock_read_char_raw: dict[str, Any]
) -> None:
    """Auto mock bluetooth."""

    client = Mock(spec_set=Client)

    SENTINEL = object()

    def _read_char(char: Characteristic, default: Any = SENTINEL):
        try:
            return char.decode(mock_read_char_raw[char.uuid])
        except KeyError:
            if default is SENTINEL:
                raise CharacteristicNotFound from KeyError
            return default

    def _read_char_raw(uuid: str, default: Any = SENTINEL):
        try:
            val = mock_read_char_raw[uuid]
            if isinstance(val, Exception):
                raise val
            return val
        except KeyError:
            if default is SENTINEL:
                raise CharacteristicNotFound from KeyError
            return default

    def _all_char():
        return set(mock_read_char_raw.keys())

    client.read_char.side_effect = _read_char
    client.read_char_raw.side_effect = _read_char_raw
    client.get_all_characteristics_uuid.side_effect = _all_char

    with patch(
        "homeassistant.components.gardena_bluetooth.config_flow.Client",
        return_value=client,
    ), patch("homeassistant.components.gardena_bluetooth.Client", return_value=client):
        yield client


@pytest.fixture(autouse=True)
def enable_all_entities():
    """Make sure all entities are enabled."""
    with patch(
        "homeassistant.components.gardena_bluetooth.coordinator.GardenaBluetoothEntity.entity_registry_enabled_default",
        new=Mock(return_value=True),
    ):
        yield
