"""
Regression tests: parsers must not raise on short payloads.

The dispatcher in `switchbot/adv_parser.py` does not pre-validate
`mfr_data` / `data` length before invoking parsers. A malformed BLE
advertisement (untrusted but range-limited input) should degrade to
an empty dict / None values, not raise an `IndexError` / `ValueError`
that bubbles up to callers.
"""

from __future__ import annotations

import pytest

from switchbot.adv_parsers.air_purifier import process_air_purifier
from switchbot.adv_parsers.art_frame import process_art_frame
from switchbot.adv_parsers.blind_tilt import process_woblindtilt
from switchbot.adv_parsers.bot import process_wohand
from switchbot.adv_parsers.bulb import process_color_bulb
from switchbot.adv_parsers.ceiling_light import process_woceiling
from switchbot.adv_parsers.climate_panel import process_climate_panel
from switchbot.adv_parsers.curtain import process_wocurtain
from switchbot.adv_parsers.fan import process_fan, process_standing_fan
from switchbot.adv_parsers.hub2 import process_wohub2
from switchbot.adv_parsers.hub3 import process_hub3
from switchbot.adv_parsers.hubmini_matter import process_hubmini_matter
from switchbot.adv_parsers.humidifier import (
    process_evaporative_humidifier,
    process_wohumidifier,
)
from switchbot.adv_parsers.keypad import process_wokeypad
from switchbot.adv_parsers.keypad_vision import (
    process_keypad_vision,
    process_keypad_vision_pro,
)
from switchbot.adv_parsers.light_strip import (
    process_candle_warmer_lamp,
    process_light,
    process_rgbic_light,
    process_wostrip,
)
from switchbot.adv_parsers.lock import (
    parse_common_data,
    process_lock2,
    process_locklite,
    process_wolock,
    process_wolock_pro,
)
from switchbot.adv_parsers.meter import process_wosensorth
from switchbot.adv_parsers.motion import process_wopresence
from switchbot.adv_parsers.plug import process_woplugmini
from switchbot.adv_parsers.remote import process_woremote
from switchbot.adv_parsers.roller_shade import process_worollershade
from switchbot.adv_parsers.smart_thermostat_radiator import (
    process_smart_thermostat_radiator,
)
from switchbot.adv_parsers.vacuum import process_vacuum, process_vacuum_k

EMPTY = b""
SHORT = b"\x00" * 4


@pytest.mark.parametrize(
    "parser",
    [
        process_air_purifier,
        process_art_frame,
        process_woblindtilt,
        process_color_bulb,
        process_woceiling,
        process_climate_panel,
        process_wocurtain,
        process_fan,
        process_standing_fan,
        process_wohub2,
        process_hub3,
        process_hubmini_matter,
        process_evaporative_humidifier,
        process_keypad_vision,
        process_keypad_vision_pro,
        process_wostrip,
        process_candle_warmer_lamp,
        process_woplugmini,
        process_worollershade,
        process_smart_thermostat_radiator,
        process_vacuum,
        process_vacuum_k,
    ],
)
@pytest.mark.parametrize("payload", [None, EMPTY, SHORT])
def test_mfr_only_parsers_return_empty_on_short(parser, payload):
    """Parsers that read only mfr_data must return {} for short payloads."""
    assert parser(None, payload) == {}


@pytest.mark.parametrize("payload", [None, EMPTY, SHORT, b"\x00" * 17])
def test_process_light_short_payload(payload):
    """process_light needs cw_offset + 2 bytes (default 18)."""
    assert process_light(None, payload) == {}


@pytest.mark.parametrize("payload", [None, EMPTY, SHORT, b"\x00" * 11])
def test_process_rgbic_light_short_payload(payload):
    """process_rgbic_light uses cw_offset=10, so needs >= 12 bytes."""
    assert process_rgbic_light(None, payload) == {}


@pytest.mark.parametrize(
    ("data", "mfr_data"),
    [
        (None, None),
        (EMPTY, None),
        (b"\x00\x00", None),
    ],
)
def test_process_wohand_short_data(data, mfr_data):
    """process_wohand must not crash on short `data`."""
    out = process_wohand(data, mfr_data)
    # Either an empty dict (both None) or all-None values
    assert "isOn" in out or out == {}
    assert out.get("battery") in (None, *out.values())


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
def test_process_woremote_short_data(data):
    out = process_woremote(data, None)
    assert out == {"battery": None}


@pytest.mark.parametrize(
    ("data", "mfr_data"),
    [
        (None, None),
        (b"\x00", None),
        (None, b"\x00"),
        (b"\x00\x00", b"\x00\x00"),
    ],
)
def test_process_wokeypad_short(data, mfr_data):
    out = process_wokeypad(data, mfr_data)
    assert out == {"battery": None, "attempt_state": None}


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00\x00"])
def test_process_wohumidifier_short_data(data):
    out = process_wohumidifier(data, None)
    assert out == {"isOn": None, "level": None, "switchMode": True}


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00\x00"])
@pytest.mark.parametrize("mfr_data", [None, EMPTY, b"\x00\x00\x00"])
def test_process_wosensorth_short(data, mfr_data):
    """process_wosensorth must not crash; returns {} when no usable payload."""
    out = process_wosensorth(data, mfr_data)
    assert isinstance(out, dict)


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00\x00"])
@pytest.mark.parametrize("mfr_data", [None, EMPTY, b"\x00\x00\x00"])
def test_process_wopresence_short(data, mfr_data):
    """process_wopresence must not crash even when both inputs are short."""
    out = process_wopresence(data, mfr_data)
    assert isinstance(out, dict)


@pytest.mark.parametrize("mfr_data", [None, EMPTY, SHORT])
def test_lock_parsers_short_mfr(mfr_data):
    assert process_locklite(None, mfr_data) == {}
    assert process_wolock(None, mfr_data) == {}
    assert parse_common_data(mfr_data) == {}
    assert process_wolock_pro(None, mfr_data) == {}
    assert process_lock2(None, mfr_data) == {}


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
def test_blind_tilt_short_data(data):
    """blind_tilt with full mfr but short data must not crash on data[2]."""
    mfr = b"\x00" * 10
    out = process_woblindtilt(data, mfr)
    assert out["battery"] is None


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
def test_curtain_short_data_with_long_mfr(data):
    """Curtain >=11 path uses data[2] for battery; must not crash on short data."""
    mfr = b"\x00" * 11
    out = process_wocurtain(data, mfr)
    assert out["battery"] is None


@pytest.mark.parametrize("data", [b"\x00\x00\x00\x00", b"\x00" * 5])
def test_curtain_short_data_only(data):
    """Curtain data-only path needs len >= 6; shorter -> {}."""
    assert process_wocurtain(data, None) == {}


@pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
def test_roller_shade_short_data(data):
    """roller_shade with short data must not crash on data[2]."""
    mfr = b"\x00" * 10
    out = process_worollershade(data, mfr)
    assert out["battery"] is None
