"""Test reproduce state for Fan."""

import pytest

from homeassistant.components.fan import (
    ATTR_DIRECTION,
    ATTR_OSCILLATING,
    ATTR_PERCENTAGE,
    ATTR_PRESET_MODE,
    ATTR_PRESET_MODES,
    DIRECTION_FORWARD,
    DIRECTION_REVERSE,
)
from homeassistant.core import State
from homeassistant.helpers.state import async_reproduce_state

from tests.common import async_mock_service


async def test_reproducing_states(hass, caplog):
    """Test reproducing Fan states."""
    hass.states.async_set("fan.entity_off", "off", {})
    hass.states.async_set("fan.entity_on", "on", {})
    hass.states.async_set("fan.entity_speed", "on", {"percentage": 100})
    hass.states.async_set("fan.entity_oscillating", "on", {"oscillating": True})
    hass.states.async_set("fan.entity_direction", "on", {"direction": "forward"})

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_calls = async_mock_service(hass, "fan", "set_percentage")

    # These calls should do nothing as entities already in desired state
    await async_reproduce_state(
        hass,
        [
            State("fan.entity_off", "off"),
            State("fan.entity_on", "on"),
            State("fan.entity_speed", "on", {"percentage": 100}),
            State("fan.entity_oscillating", "on", {"oscillating": True}),
            State("fan.entity_direction", "on", {"direction": "forward"}),
        ],
    )

    assert len(turn_on_calls) == 0
    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0

    # Test invalid state is handled
    await async_reproduce_state(hass, [State("fan.entity_off", "not_supported")])

    assert "not_supported" in caplog.text
    assert len(turn_on_calls) == 0
    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_calls) == 0

    # Make sure correct services are called
    await async_reproduce_state(
        hass,
        [
            State("fan.entity_on", "off"),
            State("fan.entity_off", "on"),
            State("fan.entity_speed", "on", {"percentage": 25}),
            State("fan.entity_oscillating", "on", {"oscillating": False}),
            State("fan.entity_direction", "on", {"direction": "reverse"}),
            # Should not raise
            State("fan.non_existing", "on"),
        ],
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {"entity_id": "fan.entity_off"}

    assert len(set_direction_calls) == 1
    assert set_direction_calls[0].domain == "fan"
    assert set_direction_calls[0].data == {
        "entity_id": "fan.entity_direction",
        "direction": "reverse",
    }

    assert len(oscillate_calls) == 1
    assert oscillate_calls[0].domain == "fan"
    assert oscillate_calls[0].data == {
        "entity_id": "fan.entity_oscillating",
        "oscillating": False,
    }

    assert len(set_percentage_calls) == 1
    assert set_percentage_calls[0].domain == "fan"
    assert set_percentage_calls[0].data == {
        "entity_id": "fan.entity_speed",
        "percentage": 25,
    }

    assert len(turn_off_calls) == 1
    assert turn_off_calls[0].domain == "fan"
    assert turn_off_calls[0].data == {"entity_id": "fan.entity_on"}


MODERN_FAN_ENTITY = "fan.modern_fan"
MODERN_FAN_OFF_PERCENTAGE10_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PERCENTAGE: 10,
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_OFF_PERCENTAGE15_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PERCENTAGE: 15,
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_ON_INVALID_STATE = {
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_OFF_PPRESET_MODE_AUTO_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PRESET_MODE: "Auto",
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_OFF_PPRESET_MODE_ECO_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PRESET_MODE: "Eco",
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_ON_PERCENTAGE10_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PERCENTAGE: 10,
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_ON_PERCENTAGE15_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PERCENTAGE: 15,
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_ON_PRESET_MODE_AUTO_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PRESET_MODE: "Auto",
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_ON_PRESET_MODE_ECO_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_FORWARD,
    ATTR_PRESET_MODE: "Eco",
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}
MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE = {
    ATTR_OSCILLATING: True,
    ATTR_DIRECTION: DIRECTION_REVERSE,
    ATTR_PRESET_MODE: "Auto",
    ATTR_PRESET_MODES: ["Auto", "Eco"],
}


@pytest.mark.parametrize(
    "start_state",
    [
        MODERN_FAN_OFF_PERCENTAGE10_STATE,
        MODERN_FAN_OFF_PERCENTAGE15_STATE,
        MODERN_FAN_OFF_PPRESET_MODE_AUTO_STATE,
        MODERN_FAN_OFF_PPRESET_MODE_ECO_STATE,
    ],
)
async def test_modern_turn_on_invalid(hass, start_state):
    """Test modern fan state reproduction, turning on with invalid state."""
    hass.states.async_set(MODERN_FAN_ENTITY, "off", start_state)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    # Turn on with an invalid config (speed, percentage, preset_modes all None)
    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_INVALID_STATE)]
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {"entity_id": MODERN_FAN_ENTITY}

    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 1
    assert set_direction_calls[0].domain == "fan"
    assert set_direction_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_DIRECTION: None,
    }
    assert len(oscillate_calls) == 1
    assert oscillate_calls[0].domain == "fan"
    assert oscillate_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_OSCILLATING: None,
    }
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0


@pytest.mark.parametrize(
    "start_state",
    [
        MODERN_FAN_OFF_PERCENTAGE10_STATE,
        MODERN_FAN_OFF_PPRESET_MODE_AUTO_STATE,
        MODERN_FAN_OFF_PPRESET_MODE_ECO_STATE,
    ],
)
async def test_modern_turn_on_percentage_from_different_speed(hass, start_state):
    """Test modern fan state reproduction, turning on with a different percentage of the state."""
    hass.states.async_set(MODERN_FAN_ENTITY, "off", start_state)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)]
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PERCENTAGE: 15,
    }

    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0


async def test_modern_turn_on_percentage_from_same_speed(hass):
    """Test modern fan state reproduction, turning on with the same percentage as in the state."""
    hass.states.async_set(MODERN_FAN_ENTITY, "off", MODERN_FAN_OFF_PERCENTAGE15_STATE)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)]
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PERCENTAGE: 15,
    }

    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0


@pytest.mark.parametrize(
    "start_state",
    [
        MODERN_FAN_OFF_PERCENTAGE10_STATE,
        MODERN_FAN_OFF_PERCENTAGE15_STATE,
        MODERN_FAN_OFF_PPRESET_MODE_ECO_STATE,
    ],
)
async def test_modern_turn_on_preset_mode_from_different_speed(hass, start_state):
    """Test modern fan state reproduction, turning on with a different preset mode from the state."""
    hass.states.async_set(MODERN_FAN_ENTITY, "off", start_state)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)]
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PRESET_MODE: "Auto",
    }

    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0


async def test_modern_turn_on_preset_mode_from_same_speed(hass):
    """Test modern fan state reproduction, turning on with the same preset mode as in the state."""
    hass.states.async_set(
        MODERN_FAN_ENTITY, "off", MODERN_FAN_OFF_PPRESET_MODE_AUTO_STATE
    )

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)]
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PRESET_MODE: "Auto",
    }

    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0


@pytest.mark.parametrize(
    "start_state",
    [
        MODERN_FAN_OFF_PERCENTAGE10_STATE,
        MODERN_FAN_OFF_PERCENTAGE15_STATE,
        MODERN_FAN_OFF_PPRESET_MODE_ECO_STATE,
    ],
)
async def test_modern_turn_on_preset_mode_reverse(hass, start_state):
    """Test modern fan state reproduction, turning on with preset mode "Auto" and reverse direction."""
    hass.states.async_set(MODERN_FAN_ENTITY, "off", start_state)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE)]
    )

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].domain == "fan"
    assert turn_on_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PRESET_MODE: "Auto",
    }
    assert len(set_direction_calls) == 1
    assert set_direction_calls[0].domain == "fan"
    assert set_direction_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_DIRECTION: DIRECTION_REVERSE,
    }

    assert len(turn_off_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0


@pytest.mark.parametrize(
    "start_state",
    [
        MODERN_FAN_ON_PERCENTAGE10_STATE,
        MODERN_FAN_ON_PERCENTAGE15_STATE,
        MODERN_FAN_ON_PRESET_MODE_ECO_STATE,
    ],
)
async def test_modern_to_preset(hass, start_state):
    """Test modern fan state reproduction, switching to preset mode "Auto"."""
    hass.states.async_set(MODERN_FAN_ENTITY, "on", start_state)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)]
    )

    assert len(turn_on_calls) == 0
    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 1
    assert set_preset_mode[0].domain == "fan"
    assert set_preset_mode[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PRESET_MODE: "Auto",
    }


@pytest.mark.parametrize(
    "start_state",
    [
        MODERN_FAN_ON_PERCENTAGE10_STATE,
        MODERN_FAN_ON_PRESET_MODE_AUTO_STATE,
        MODERN_FAN_ON_PRESET_MODE_ECO_STATE,
    ],
)
async def test_modern_to_percentage(hass, start_state):
    """Test modern fan state reproduction, switching to 15% speed."""
    hass.states.async_set(MODERN_FAN_ENTITY, "on", start_state)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)]
    )

    assert len(turn_on_calls) == 0
    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 0
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 1
    assert set_percentage_mode[0].domain == "fan"
    assert set_percentage_mode[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_PERCENTAGE: 15,
    }
    assert len(set_preset_mode) == 0


async def test_modern_direction(hass):
    """Test modern fan state reproduction, switching only direction state."""
    hass.states.async_set(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)

    turn_on_calls = async_mock_service(hass, "fan", "turn_on")
    turn_off_calls = async_mock_service(hass, "fan", "turn_off")
    set_direction_calls = async_mock_service(hass, "fan", "set_direction")
    oscillate_calls = async_mock_service(hass, "fan", "oscillate")
    set_percentage_mode = async_mock_service(hass, "fan", "set_percentage")
    set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode")

    await hass.helpers.state.async_reproduce_state(
        [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE)]
    )

    assert len(turn_on_calls) == 0
    assert len(turn_off_calls) == 0
    assert len(set_direction_calls) == 1
    assert set_direction_calls[0].domain == "fan"
    assert set_direction_calls[0].data == {
        "entity_id": MODERN_FAN_ENTITY,
        ATTR_DIRECTION: DIRECTION_REVERSE,
    }
    assert len(oscillate_calls) == 0
    assert len(set_percentage_mode) == 0
    assert len(set_preset_mode) == 0