"""Test add-ons schema to UI schema convertion."""
from pathlib import Path

import pytest
import voluptuous as vol

from supervisor.addons.options import AddonOptions, UiOptions
from supervisor.hardware.data import Device

MOCK_ADDON_NAME = "Mock Add-on"
MOCK_ADDON_SLUG = "mock_addon"


def test_simple_schema(coresys):
    """Test with simple schema."""
    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "fires": True, "alias": "test"})

    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "fires": True})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "fires": "hah"})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "fires": True})


def test_complex_schema_list(coresys):
    """Test with complex list schema."""
    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "extend": ["str"]},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "extend": ["test", "blu"]})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "extend": ["str"]},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "extend": ["test", 1]})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "extend": ["str"]},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "extend": "test"})


def test_optional_schema_list(coresys):
    """Test with an optional list schema."""
    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "extend": ["str?"]},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234"})

    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "extend": ["str?"]},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "extend": []})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "extend": ["str"]},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234"})

    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "extend": ["str"]},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "extend": []})


def test_complex_schema_dict(coresys):
    """Test with complex dict schema."""
    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "extend": {"test": "int"}},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "extend": {"test": 1}})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "extend": {"test": "int"}},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "extend": {"wrong": 1}})

    with pytest.raises(vol.error.Invalid):
        AddonOptions(
            coresys,
            {"name": "str", "password": "password", "extend": ["str"]},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "extend": "test"})


def test_simple_device_schema(coresys):
    """Test with simple schema."""
    for device in (
        Device(
            "ttyACM0",
            Path("/dev/ttyACM0"),
            Path("/sys/bus/usb/002"),
            "tty",
            None,
            [],
            {"ID_VENDOR": "xy"},
            [],
        ),
        Device(
            "ttyUSB0",
            Path("/dev/ttyUSB0"),
            Path("/sys/bus/usb/001"),
            "tty",
            None,
            [Path("/dev/ttyS1"), Path("/dev/serial/by-id/xyx")],
            {"ID_VENDOR": "xy"},
            [],
        ),
        Device(
            "ttyS0",
            Path("/dev/ttyS0"),
            Path("/sys/bus/usb/003"),
            "tty",
            None,
            [],
            {},
            [],
        ),
        Device(
            "video1",
            Path("/dev/video1"),
            Path("/sys/bus/usb/004"),
            "misc",
            None,
            [],
            {"ID_VENDOR": "xy"},
            [],
        ),
    ):
        coresys.hardware.update_device(device)

    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "input": "device"},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "input": "/dev/ttyUSB0"})

    data = AddonOptions(
        coresys,
        {"name": "str", "password": "password", "input": "device"},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "input": "/dev/serial/by-id/xyx"})
    assert data["input"] == "/dev/ttyUSB0"

    assert AddonOptions(
        coresys,
        {"name": "str", "password": "password", "input": "device(subsystem=tty)"},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )({"name": "Pascal", "password": "1234", "input": "/dev/ttyACM0"})

    with pytest.raises(vol.error.Invalid):
        assert AddonOptions(
            coresys,
            {"name": "str", "password": "password", "input": "device"},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "input": "/dev/not_exists"})

    with pytest.raises(vol.error.Invalid):
        assert AddonOptions(
            coresys,
            {"name": "str", "password": "password", "input": "device(subsystem=tty)"},
            MOCK_ADDON_NAME,
            MOCK_ADDON_SLUG,
        )({"name": "Pascal", "password": "1234", "input": "/dev/video1"})


def test_simple_schema_password(coresys):
    """Test with simple schema password pwned."""
    validate = AddonOptions(
        coresys,
        {"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
        MOCK_ADDON_NAME,
        MOCK_ADDON_SLUG,
    )

    assert validate(
        {"name": "Pascal", "password": "1234", "fires": True, "alias": "test"}
    )

    assert validate.pwned == {"7110eda4d09e062aa5e4a390b0a572ac0d2c0220"}

    validate.pwned.clear()
    assert validate({"name": "Pascal", "password": "", "fires": True, "alias": "test"})

    assert not validate.pwned


def test_ui_simple_schema(coresys):
    """Test with simple schema."""
    assert UiOptions(coresys)(
        {"name": "str", "password": "password", "fires": "bool", "alias": "str?"},
    ) == [
        {"name": "name", "required": True, "type": "string"},
        {"format": "password", "name": "password", "required": True, "type": "string"},
        {"name": "fires", "required": True, "type": "boolean"},
        {"name": "alias", "optional": True, "type": "string"},
    ]


def test_ui_group_schema(coresys):
    """Test with group schema."""
    assert UiOptions(coresys)(
        {
            "name": "str",
            "password": "password",
            "fires": "bool",
            "alias": "str?",
            "extended": {"name": "str", "data": ["str"], "path": "str?"},
        },
    ) == [
        {"name": "name", "required": True, "type": "string"},
        {"format": "password", "name": "password", "required": True, "type": "string"},
        {"name": "fires", "required": True, "type": "boolean"},
        {"name": "alias", "optional": True, "type": "string"},
        {
            "multiple": False,
            "name": "extended",
            "optional": True,
            "schema": [
                {"name": "name", "required": True, "type": "string"},
                {"multiple": True, "name": "data", "required": True, "type": "string"},
                {"name": "path", "optional": True, "type": "string"},
            ],
            "type": "schema",
        },
    ]


def test_ui_group_list_schema(coresys):
    """Test with group schema."""
    assert UiOptions(coresys)(
        {
            "name": "str",
            "password": "password",
            "fires": "bool",
            "alias": "str?",
            "extended": [{"name": "str", "data": ["str?"], "path": "str?"}],
        },
    ) == [
        {"name": "name", "required": True, "type": "string"},
        {"format": "password", "name": "password", "required": True, "type": "string"},
        {"name": "fires", "required": True, "type": "boolean"},
        {"name": "alias", "optional": True, "type": "string"},
        {
            "multiple": True,
            "name": "extended",
            "optional": True,
            "schema": [
                {"name": "name", "required": True, "type": "string"},
                {"multiple": True, "name": "data", "optional": True, "type": "string"},
                {"name": "path", "optional": True, "type": "string"},
            ],
            "type": "schema",
        },
    ]


def test_ui_simple_device_schema(coresys):
    """Test with simple schema."""
    for device in (
        Device(
            "ttyACM0",
            Path("/dev/ttyACM0"),
            Path("/sys/bus/usb/002"),
            "tty",
            None,
            [],
            {"ID_VENDOR": "xy"},
            [],
        ),
        Device(
            "ttyUSB0",
            Path("/dev/ttyUSB0"),
            Path("/sys/bus/usb/001"),
            "tty",
            None,
            [Path("/dev/ttyS1"), Path("/dev/serial/by-id/xyx")],
            {"ID_VENDOR": "xy"},
            [],
        ),
        Device(
            "ttyS0",
            Path("/dev/ttyS0"),
            Path("/sys/bus/usb/003"),
            "tty",
            None,
            [],
            {},
            [],
        ),
        Device(
            "video1",
            Path("/dev/video1"),
            Path("/sys/bus/usb/004"),
            "misc",
            None,
            [],
            {"ID_VENDOR": "xy"},
            [],
        ),
    ):
        coresys.hardware.update_device(device)

    data = UiOptions(coresys)(
        {
            "name": "str",
            "password": "password",
            "fires": "bool",
            "alias": "str?",
            "input": "device(subsystem=tty)",
        },
    )

    assert sorted(data[-1]["options"]) == sorted(
        [
            "/dev/serial/by-id/xyx",
            "/dev/ttyACM0",
            "/dev/ttyS0",
        ]
    )
    assert data[-1]["type"] == "select"


def test_ui_simple_device_schema_no_filter(coresys):
    """Test with simple schema without filter."""
    for device in (
        Device(
            "ttyACM0",
            Path("/dev/ttyACM0"),
            Path("/sys/bus/usb/002"),
            "tty",
            None,
            [],
            {"ID_VENDOR": "xy"},
            [],
        ),
        Device(
            "ttyUSB0",
            Path("/dev/ttyUSB0"),
            Path("/sys/bus/usb/001"),
            "tty",
            None,
            [Path("/dev/ttyS1"), Path("/dev/serial/by-id/xyx")],
            {"ID_VENDOR": "xy"},
            [],
        ),
        Device(
            "ttyS0",
            Path("/dev/ttyS0"),
            Path("/sys/bus/usb/003"),
            "tty",
            None,
            [],
            {},
            [],
        ),
        Device(
            "video1",
            Path("/dev/video1"),
            Path("/sys/bus/usb/004"),
            "misc",
            None,
            [],
            {"ID_VENDOR": "xy"},
            [],
        ),
    ):
        coresys.hardware.update_device(device)

    data = UiOptions(coresys)(
        {
            "name": "str",
            "password": "password",
            "fires": "bool",
            "alias": "str?",
            "input": "device",
        },
    )

    assert sorted(data[-1]["options"]) == sorted(
        ["/dev/serial/by-id/xyx", "/dev/ttyACM0", "/dev/ttyS0", "/dev/video1"]
    )
    assert data[-1]["type"] == "select"


def test_log_entry(coresys, caplog):
    """Test log entry when no option match in schema."""
    options = AddonOptions(coresys, {}, MOCK_ADDON_NAME, MOCK_ADDON_SLUG)(
        {"test": "str"}
    )
    assert isinstance(options, dict)
    assert not options
    assert (
        "Option 'test' does not exist in the schema for Mock Add-on (mock_addon)"
        in caplog.text
    )
