from pytest_mock import MockerFixture

from kasa import DeviceType, Module
from kasa.cli.vacuum import vacuum

from ..device_fixtures import plug_iot
from ..device_fixtures import vacuum as vacuum_devices


@vacuum_devices
async def test_vacuum_records_group(dev, mocker: MockerFixture, runner):
    """Test that vacuum records calls the expected methods."""
    rec = dev.modules.get(Module.CleanRecords)
    assert rec

    res = await runner.invoke(vacuum, ["records"], obj=dev, catch_exceptions=False)

    latest = rec.parsed_data.last_clean
    expected = (
        f"Totals: {rec.total_clean_area} {rec.area_unit} in {rec.total_clean_time} "
        f"(cleaned {rec.total_clean_count} times)\n"
        f"Last clean: {latest.clean_area} {rec.area_unit} @ {latest.clean_time}"
    )
    assert expected in res.output
    assert res.exit_code == 0


@vacuum_devices
async def test_vacuum_records_list(dev, mocker: MockerFixture, runner):
    """Test that vacuum records list calls the expected methods."""
    rec = dev.modules.get(Module.CleanRecords)
    assert rec

    res = await runner.invoke(
        vacuum, ["records", "list"], obj=dev, catch_exceptions=False
    )

    data = rec.parsed_data
    for record in data.records:
        expected = (
            f"* {record.timestamp}: cleaned {record.clean_area} {rec.area_unit}"
            f" in {record.clean_time}"
        )
        assert expected in res.output
    assert res.exit_code == 0


@vacuum_devices
async def test_vacuum_consumables(dev, runner):
    """Test that vacuum consumables calls the expected methods."""
    cons = dev.modules.get(Module.Consumables)
    assert cons

    res = await runner.invoke(vacuum, ["consumables"], obj=dev, catch_exceptions=False)

    expected = ""
    for c in cons.consumables.values():
        expected += f"{c.name} ({c.id}): {c.used} used, {c.remaining} remaining\n"

    assert expected in res.output
    assert res.exit_code == 0


@vacuum_devices
async def test_vacuum_consumables_reset(dev, mocker: MockerFixture, runner):
    """Test that vacuum consumables reset calls the expected methods."""
    cons = dev.modules.get(Module.Consumables)
    assert cons

    reset_consumable_mock = mocker.spy(cons, "reset_consumable")
    for c_id in cons.consumables:
        reset_consumable_mock.reset_mock()
        res = await runner.invoke(
            vacuum, ["consumables", "reset", c_id], obj=dev, catch_exceptions=False
        )
        reset_consumable_mock.assert_awaited_once_with(c_id)
        assert f"Consumable {c_id} reset" in res.output
        assert res.exit_code == 0

    res = await runner.invoke(
        vacuum, ["consumables", "reset", "foobar"], obj=dev, catch_exceptions=False
    )
    expected = (
        "Consumable foobar not found in "
        f"device consumables: {', '.join(cons.consumables.keys())}."
    )
    assert expected in res.output.replace("\n", "")
    assert res.exit_code != 0


@plug_iot
async def test_non_vacuum(dev, mocker: MockerFixture, runner):
    """Test that vacuum commands return an error if executed on a non-vacuum."""
    assert dev.device_type is not DeviceType.Vacuum

    res = await runner.invoke(vacuum, ["records"], obj=dev, catch_exceptions=False)
    assert "This device does not support records" in res.output
    assert res.exit_code != 0

    res = await runner.invoke(
        vacuum, ["records", "list"], obj=dev, catch_exceptions=False
    )
    assert "This device does not support records" in res.output
    assert res.exit_code != 0

    res = await runner.invoke(vacuum, ["consumables"], obj=dev, catch_exceptions=False)
    assert "This device does not support consumables" in res.output
    assert res.exit_code != 0

    res = await runner.invoke(
        vacuum, ["consumables", "reset", "foobar"], obj=dev, catch_exceptions=False
    )
    assert "This device does not support consumables" in res.output
    assert res.exit_code != 0