"""Tests for the state log formatter."""

from __future__ import annotations

from aioesphomeapi.model import (
    AlarmControlPanelEntityState,
    AlarmControlPanelState,
    BinarySensorInfo,
    BinarySensorState,
    CameraState,
    ClimateAction,
    ClimateFanMode,
    ClimateInfo,
    ClimateMode,
    ClimatePreset,
    ClimateState,
    ClimateSwingMode,
    CoverOperation,
    CoverState,
    DateState,
    DateTimeState,
    Event,
    EventInfo,
    FanDirection,
    FanState,
    LightState,
    LockEntityState,
    LockState,
    MediaPlayerEntityState,
    MediaPlayerState,
    NumberState,
    SelectState,
    SensorInfo,
    SensorState,
    SirenState,
    SwitchState,
    TextSensorState,
    TextState,
    TimeState,
    UpdateState,
    ValveOperation,
    ValveState,
    WaterHeaterMode,
    WaterHeaterState,
)
from aioesphomeapi.state_log_formatter import format_state_log


def test_sensor_basic() -> None:
    info = SensorInfo(name="CO2", key=1, unit_of_measurement="ppm", accuracy_decimals=0)
    state = SensorState(key=1, state=420.0)
    assert format_state_log(state, info) == "[S][sensor]: 'CO2' >> 420 ppm"


def test_sensor_with_decimals() -> None:
    info = SensorInfo(
        name="Temperature", key=2, unit_of_measurement="°C", accuracy_decimals=2
    )
    state = SensorState(key=2, state=35.63)
    assert format_state_log(state, info) == "[S][sensor]: 'Temperature' >> 35.63 °C"


def test_sensor_missing_state() -> None:
    info = SensorInfo(name="CO2", key=1, unit_of_measurement="ppm")
    state = SensorState(key=1, state=0.0, missing_state=True)
    assert format_state_log(state, info) is None


def test_sensor_no_info() -> None:
    state = SensorState(key=1, state=42.0)
    assert format_state_log(state, None) == "[S][sensor]: '?' >> 42.0"


def test_binary_sensor_on() -> None:
    info = BinarySensorInfo(name="Motion", key=1)
    state = BinarySensorState(key=1, state=True)
    assert format_state_log(state, info) == "[S][binary_sensor]: 'Motion' >> ON"


def test_binary_sensor_off() -> None:
    info = BinarySensorInfo(name="Motion", key=1)
    state = BinarySensorState(key=1, state=False)
    assert format_state_log(state, info) == "[S][binary_sensor]: 'Motion' >> OFF"


def test_binary_sensor_missing() -> None:
    info = BinarySensorInfo(name="Motion", key=1)
    state = BinarySensorState(key=1, state=False, missing_state=True)
    assert format_state_log(state, info) is None


def test_switch_on() -> None:
    info = BinarySensorInfo(name="Relay", key=1)
    state = SwitchState(key=1, state=True)
    assert format_state_log(state, info) == "[S][switch]: 'Relay' >> ON"


def test_switch_off() -> None:
    info = BinarySensorInfo(name="Relay", key=1)
    state = SwitchState(key=1, state=False)
    assert format_state_log(state, info) == "[S][switch]: 'Relay' >> OFF"


def test_siren_on() -> None:
    info = BinarySensorInfo(name="Alarm", key=1)
    state = SirenState(key=1, state=True)
    assert format_state_log(state, info) == "[S][siren]: 'Alarm' >> ON"


def test_siren_off() -> None:
    info = BinarySensorInfo(name="Alarm", key=1)
    state = SirenState(key=1, state=False)
    assert format_state_log(state, info) == "[S][siren]: 'Alarm' >> OFF"


def test_datetime_basic() -> None:
    info = BinarySensorInfo(name="Next Run", key=1)
    state = DateTimeState(key=1, epoch_seconds=1_700_000_000)
    assert (
        format_state_log(state, info)
        == "[S][datetime]: 'Next Run' >> 2023-11-14 22:13:20"
    )


def test_datetime_missing() -> None:
    state = DateTimeState(key=1, missing_state=True)
    assert format_state_log(state, None) is None


def test_datetime_out_of_range_falls_back_to_epoch() -> None:
    info = BinarySensorInfo(name="Next Run", key=1)
    state = DateTimeState(key=1, epoch_seconds=10**18)
    assert (
        format_state_log(state, info)
        == "[S][datetime]: 'Next Run' >> 1000000000000000000"
    )


def test_text_sensor_basic() -> None:
    info = BinarySensorInfo(name="Version", key=1)
    state = TextSensorState(key=1, state="2026.3.0")
    assert format_state_log(state, info) == "[S][text_sensor]: 'Version' >> '2026.3.0'"


def test_text_sensor_missing() -> None:
    info = BinarySensorInfo(name="Version", key=1)
    state = TextSensorState(key=1, state="", missing_state=True)
    assert format_state_log(state, info) is None


def test_number_basic() -> None:
    info = BinarySensorInfo(name="Brightness", key=1)
    state = NumberState(key=1, state=50.0)
    assert format_state_log(state, info) == "[S][number]: 'Brightness' >> 50.00"


def test_number_missing() -> None:
    info = BinarySensorInfo(name="Brightness", key=1)
    state = NumberState(key=1, state=0.0, missing_state=True)
    assert format_state_log(state, info) is None


def test_select_basic() -> None:
    info = BinarySensorInfo(name="Mode", key=1)
    state = SelectState(key=1, state="eco")
    assert format_state_log(state, info) == "[S][select]: 'Mode' >> eco"


def test_select_missing() -> None:
    state = SelectState(key=1, state="", missing_state=True)
    assert format_state_log(state, None) is None


def test_lock_locked() -> None:
    info = BinarySensorInfo(name="Front Door", key=1)
    state = LockEntityState(key=1, state=LockState.LOCKED)
    assert format_state_log(state, info) == "[S][lock]: 'Front Door' >> LOCKED"


def test_lock_unlocked() -> None:
    info = BinarySensorInfo(name="Front Door", key=1)
    state = LockEntityState(key=1, state=LockState.UNLOCKED)
    assert format_state_log(state, info) == "[S][lock]: 'Front Door' >> UNLOCKED"


def test_event_basic() -> None:
    info = EventInfo(name="Button", key=1)
    state = Event(key=1, event_type="press")
    assert format_state_log(state, info) == "[S][event]: 'Button' >> 'press'"


def test_text_basic() -> None:
    info = BinarySensorInfo(name="Name", key=1)
    state = TextState(key=1, state="hello")
    assert format_state_log(state, info) == "[S][text]: 'Name' >> 'hello'"


def test_text_missing() -> None:
    state = TextState(key=1, state="", missing_state=True)
    assert format_state_log(state, None) is None


def test_date_basic() -> None:
    info = BinarySensorInfo(name="Birthday", key=1)
    state = DateState(key=1, year=2026, month=3, day=24)
    assert format_state_log(state, info) == "[S][datetime]: 'Birthday' >> 2026-3-24"


def test_date_missing() -> None:
    state = DateState(key=1, missing_state=True)
    assert format_state_log(state, None) is None


def test_time_basic() -> None:
    info = BinarySensorInfo(name="Alarm", key=1)
    state = TimeState(key=1, hour=7, minute=30, second=0)
    assert format_state_log(state, info) == "[S][datetime]: 'Alarm' >> 07:30:00"


def test_time_missing() -> None:
    state = TimeState(key=1, missing_state=True)
    assert format_state_log(state, None) is None


def test_cover_closed() -> None:
    info = BinarySensorInfo(name="Garage", key=1)
    state = CoverState(key=1, position=0.0, current_operation=CoverOperation.IDLE)
    result = format_state_log(state, info)
    assert result is not None
    lines = result.split("\n")
    assert lines[0] == "[S][cover]: 'Garage' >>"
    assert lines[1] == "[S][cover]:   State: CLOSED"
    assert lines[2] == "[S][cover]:   Current Operation: IDLE"


def test_cover_open() -> None:
    info = BinarySensorInfo(name="Garage", key=1)
    state = CoverState(key=1, position=1.0, current_operation=CoverOperation.IDLE)
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][cover]:   State: OPEN" in result


def test_cover_partial() -> None:
    info = BinarySensorInfo(name="Garage", key=1)
    state = CoverState(key=1, position=0.5, current_operation=CoverOperation.IS_OPENING)
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][cover]:   Position: 50%" in result
    assert "[S][cover]:   Current Operation: IS_OPENING" in result


def test_valve_closed() -> None:
    info = BinarySensorInfo(name="Water", key=1)
    state = ValveState(key=1, position=0.0, current_operation=ValveOperation.IDLE)
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][valve]:   State: CLOSED" in result


def test_valve_open() -> None:
    info = BinarySensorInfo(name="Water", key=1)
    state = ValveState(key=1, position=1.0, current_operation=ValveOperation.IDLE)
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][valve]:   State: OPEN" in result


def test_valve_partial() -> None:
    info = BinarySensorInfo(name="Water", key=1)
    state = ValveState(
        key=1, position=0.75, current_operation=ValveOperation.IS_OPENING
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][valve]:   Position: 75%" in result
    assert "[S][valve]:   Current Operation: IS_OPENING" in result


def test_fan_on_with_speed() -> None:
    info = BinarySensorInfo(name="Fan", key=1)
    state = FanState(key=1, state=True, speed_level=3)
    result = format_state_log(state, info)
    assert result is not None
    lines = result.split("\n")
    assert lines[0] == "[S][fan]: 'Fan' >> ON"
    assert lines[1] == "[S][fan]:   Speed: 3"


def test_fan_off() -> None:
    info = BinarySensorInfo(name="Fan", key=1)
    state = FanState(key=1, state=False)
    assert format_state_log(state, info) == "[S][fan]: 'Fan' >> OFF"


def test_fan_with_oscillating_and_direction() -> None:
    info = BinarySensorInfo(name="Fan", key=1)
    state = FanState(
        key=1,
        state=True,
        oscillating=True,
        direction=FanDirection.REVERSE,
        preset_mode="turbo",
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][fan]:   Oscillating: YES" in result
    assert "[S][fan]:   Direction: REVERSE" in result
    assert "[S][fan]:   Preset Mode: turbo" in result


def test_light_on_with_brightness() -> None:
    info = BinarySensorInfo(name="Light", key=1)
    state = LightState(key=1, state=True, brightness=0.5)
    result = format_state_log(state, info)
    assert result is not None
    lines = result.split("\n")
    assert lines[0] == "[S][light]: 'Light' >> ON"
    assert lines[1] == "[S][light]:   Brightness: 50%"


def test_light_off() -> None:
    info = BinarySensorInfo(name="Light", key=1)
    state = LightState(key=1, state=False)
    assert format_state_log(state, info) == "[S][light]: 'Light' >> OFF"


def test_light_rgb() -> None:
    info = BinarySensorInfo(name="RGB", key=1)
    state = LightState(key=1, state=True, brightness=1.0, red=1.0, green=0.5, blue=0.0)
    result = format_state_log(state, info)
    assert result is not None
    assert "Red: 100%, Green: 50%, Blue: 0%" in result


def test_light_color_temperature_and_effect() -> None:
    info = BinarySensorInfo(name="Strip", key=1)
    state = LightState(
        key=1, state=True, brightness=0.8, color_temperature=250.0, effect="Rainbow"
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][light]:   Color temperature: 250.0 mireds" in result
    assert "[S][light]:   Effect: 'Rainbow'" in result


def test_climate_basic() -> None:
    info = ClimateInfo(name="HVAC", key=1)
    state = ClimateState(
        key=1,
        mode=ClimateMode.HEAT,
        current_temperature=20.5,
        target_temperature=22.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    lines = result.split("\n")
    assert lines[0] == "[S][climate]: 'HVAC' >>"
    assert lines[1] == "[S][climate]:   Mode: HEAT"
    assert "[S][climate]:   Current Temperature: 20.50°C" in result
    assert "[S][climate]:   Target Temperature: 22.00°C" in result


def test_climate_full_features() -> None:
    info = ClimateInfo(name="AC", key=1)
    state = ClimateState(
        key=1,
        mode=ClimateMode.COOL,
        action=ClimateAction.COOLING,
        fan_mode=ClimateFanMode.HIGH,
        custom_fan_mode="turbo",
        preset=ClimatePreset.BOOST,
        custom_preset="my_preset",
        swing_mode=ClimateSwingMode.BOTH,
        current_temperature=25.0,
        target_temperature=20.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][climate]:   Action: COOLING" in result
    assert "[S][climate]:   Fan Mode: HIGH" in result
    assert "[S][climate]:   Custom Fan Mode: turbo" in result
    assert "[S][climate]:   Preset: BOOST" in result
    assert "[S][climate]:   Custom Preset: my_preset" in result
    assert "[S][climate]:   Swing Mode: BOTH" in result


def test_climate_two_point_target_temperature() -> None:
    info = ClimateInfo(
        name="HVAC",
        key=1,
        supports_two_point_target_temperature=True,
    )
    state = ClimateState(
        key=1,
        mode=ClimateMode.HEAT_COOL,
        current_temperature=22.5,
        target_temperature_low=21.0,
        target_temperature_high=24.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][climate]:   Current Temperature: 22.50°C" in result
    assert "[S][climate]:   Target Temperature: Low: 21.00°C High: 24.00°C" in result
    # Single target_temperature should NOT appear (protobuf default 0.0)
    assert "Target Temperature: 0.00°C" not in result


def test_climate_humidity() -> None:
    info = ClimateInfo(
        name="HVAC",
        key=1,
        supports_current_humidity=True,
        supports_target_humidity=True,
    )
    state = ClimateState(
        key=1,
        mode=ClimateMode.HEAT,
        current_temperature=20.0,
        target_temperature=22.0,
        current_humidity=45.0,
        target_humidity=50.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][climate]:   Current Humidity: 45%" in result
    assert "[S][climate]:   Target Humidity: 50%" in result


def test_climate_humidity_zero_values() -> None:
    info = ClimateInfo(
        name="HVAC",
        key=1,
        supports_current_humidity=True,
        supports_target_humidity=True,
    )
    state = ClimateState(
        key=1,
        mode=ClimateMode.HEAT,
        current_humidity=0.0,
        target_humidity=0.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][climate]:   Current Humidity: 0%" in result
    assert "[S][climate]:   Target Humidity: 0%" in result


def test_alarm_disarmed() -> None:
    info = BinarySensorInfo(name="Alarm", key=1)
    state = AlarmControlPanelEntityState(key=1, state=AlarmControlPanelState.DISARMED)
    assert (
        format_state_log(state, info) == "[S][alarm_control_panel]: 'Alarm' >> DISARMED"
    )


def test_media_player_playing() -> None:
    info = BinarySensorInfo(name="Speaker", key=1)
    state = MediaPlayerEntityState(key=1, state=MediaPlayerState.PLAYING, volume=0.75)
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][media_player]: 'Speaker' >> PLAYING" in result
    assert "[S][media_player]:   Volume: 75%" in result


def test_media_player_idle() -> None:
    info = BinarySensorInfo(name="Speaker", key=1)
    state = MediaPlayerEntityState(key=1, state=MediaPlayerState.IDLE)
    assert format_state_log(state, info) == "[S][media_player]: 'Speaker' >> IDLE"


def test_media_player_muted() -> None:
    info = BinarySensorInfo(name="Speaker", key=1)
    state = MediaPlayerEntityState(
        key=1, state=MediaPlayerState.PLAYING, volume=0.5, muted=True
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][media_player]:   Muted: YES" in result


def test_water_heater_basic() -> None:
    info = BinarySensorInfo(name="Boiler", key=1)
    state = WaterHeaterState(
        key=1,
        mode=WaterHeaterMode.ECO,
        current_temperature=45.0,
        target_temperature=50.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][water_heater]: 'Boiler' >>" in result
    assert "[S][water_heater]:   Mode: ECO" in result
    assert "[S][water_heater]:   Current Temperature: 45.00°C" in result


def test_update_basic() -> None:
    info = BinarySensorInfo(name="Firmware", key=1)
    state = UpdateState(key=1, current_version="2026.3.0", latest_version="2026.4.0")
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][update]:   Current Version: 2026.3.0" in result
    assert "[S][update]:   Latest Version: 2026.4.0" in result


def test_update_missing() -> None:
    state = UpdateState(key=1, missing_state=True)
    assert format_state_log(state, None) is None


def test_update_with_progress() -> None:
    info = BinarySensorInfo(name="Firmware", key=1)
    state = UpdateState(
        key=1,
        current_version="2026.3.0",
        latest_version="2026.4.0",
        has_progress=True,
        progress=75.0,
    )
    result = format_state_log(state, info)
    assert result is not None
    assert "[S][update]:   Progress: 75%" in result


def test_camera_returns_none() -> None:
    state = CameraState(key=1)
    assert format_state_log(state, None) is None
