"""Test Roborock Time platform."""

from collections.abc import Callable
from datetime import time, timedelta
from typing import Any

from freezegun.api import FrozenDateTimeFactory
import pytest
import roborock
from roborock.data import DnDTimer, RoborockBaseTimer, ValleyElectricityTimer

from homeassistant.components.time import SERVICE_SET_VALUE
from homeassistant.const import STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .conftest import FakeDevice

from tests.common import MockConfigEntry, async_fire_time_changed


@pytest.fixture
def platforms() -> list[Platform]:
    """Fixture to set platforms used in the test."""
    return [Platform.TIME]


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize(
    ("entity_id", "start_state", "expected_call", "expected_args", "end_state"),
    [
        (
            "time.roborock_s7_maxv_do_not_disturb_begin",
            "22:00:00",
            lambda x: x.v1_properties.dnd.set_dnd_timer,
            DnDTimer(start_hour=1, start_minute=1, end_hour=7, end_minute=0, enabled=1),
            "01:01:00",
        ),
        (
            "time.roborock_s7_maxv_do_not_disturb_end",
            "07:00:00",
            lambda x: x.v1_properties.dnd.set_dnd_timer,
            DnDTimer(
                start_hour=22, start_minute=0, end_hour=1, end_minute=1, enabled=1
            ),
            "01:01:00",
        ),
        (
            "time.roborock_s7_maxv_off_peak_start",
            "23:00:00",
            lambda x: x.v1_properties.valley_electricity_timer.set_timer,
            ValleyElectricityTimer(
                start_hour=1, start_minute=1, end_hour=7, end_minute=0, enabled=1
            ),
            "01:01:00",
        ),
        (
            "time.roborock_s7_maxv_off_peak_end",
            "07:00:00",
            lambda x: x.v1_properties.valley_electricity_timer.set_timer,
            ValleyElectricityTimer(
                start_hour=23, start_minute=0, end_hour=1, end_minute=1, enabled=1
            ),
            "01:01:00",
        ),
    ],
)
async def test_update_success(
    hass: HomeAssistant,
    setup_entry: MockConfigEntry,
    fake_vacuum: FakeDevice,
    entity_id: str,
    start_state: str,
    end_state: str,
    expected_call: Callable[[FakeDevice], Any],
    expected_args: RoborockBaseTimer,
) -> None:
    """Test turning switch entities on and off."""
    # Ensure that the entity exist, as these test can pass even if there is no entity.
    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state == start_state

    await hass.services.async_call(
        "time",
        SERVICE_SET_VALUE,
        service_data={"time": time(hour=1, minute=1)},
        blocking=True,
        target={"entity_id": entity_id},
    )

    call = expected_call(fake_vacuum)
    assert call.call_count == 1
    # Since we update the begin or end time separately: Verify that
    # the args are built properly by reading the existing value and
    # only updating the relevant fields.
    assert call.call_args == ((expected_args,),)

    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state == end_state


@pytest.mark.parametrize(
    ("entity_id"),
    [
        ("time.roborock_s7_maxv_do_not_disturb_begin"),
    ],
)
async def test_update_failure(
    hass: HomeAssistant,
    setup_entry: MockConfigEntry,
    entity_id: str,
    fake_vacuum: FakeDevice,
) -> None:
    """Test turning switch entities on and off."""
    # Ensure that the entity exist, as these test can pass even if there is no entity.
    assert hass.states.get(entity_id) is not None
    fake_vacuum.v1_properties.dnd.set_dnd_timer.side_effect = (
        roborock.exceptions.RoborockTimeout
    )
    with pytest.raises(HomeAssistantError, match="Failed to update Roborock options"):
        await hass.services.async_call(
            "time",
            SERVICE_SET_VALUE,
            service_data={"time": time(hour=1, minute=1)},
            blocking=True,
            target={"entity_id": entity_id},
        )
    assert fake_vacuum.v1_properties.dnd.set_dnd_timer.call_count == 1


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize(
    ("entity_id", "trait", "missing_attribute"),
    [
        (
            "time.roborock_s7_maxv_do_not_disturb_begin",
            lambda x: x.v1_properties.dnd,
            "start_hour",
        ),
        (
            "time.roborock_s7_maxv_do_not_disturb_begin",
            lambda x: x.v1_properties.dnd,
            "start_minute",
        ),
        (
            "time.roborock_s7_maxv_do_not_disturb_end",
            lambda x: x.v1_properties.dnd,
            "end_hour",
        ),
        (
            "time.roborock_s7_maxv_off_peak_start",
            lambda x: x.v1_properties.valley_electricity_timer,
            "start_minute",
        ),
    ],
)
async def test_missing_value(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    setup_entry: MockConfigEntry,
    fake_vacuum: FakeDevice,
    entity_id: str,
    trait: Callable[[FakeDevice], Any],
    missing_attribute: str,
) -> None:
    """Test that a missing time component reports as unknown instead of raising."""
    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state != STATE_UNKNOWN

    setattr(trait(fake_vacuum), missing_attribute, None)
    freezer.tick(timedelta(seconds=31))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state == STATE_UNKNOWN
