import pathlib
import unittest.mock
import re

import pytest

from acpi_backlight import Backlight, backlight_eval

# pylint: disable=protected-access
# pylint: disable=redefined-outer-name; fixture


def test_init_default():
    backlight = Backlight()
    assert backlight._acpi_dir_path.as_posix() == "/sys/class/backlight/intel_backlight"


@pytest.mark.parametrize("name", ["intel_backlight", "other"])
def test_init(name):
    backlight = Backlight(name=name)
    assert backlight._acpi_dir_path.as_posix() == "/sys/class/backlight/" + name


def test__brightness_absolute_set_permission_denied() -> None:
    backlight = Backlight()
    with unittest.mock.patch(
        "pathlib.Path.open",
        side_effect=PermissionError(
            "[Errno 13] Permission denied:"
            " '/sys/class/backlight/intel_backlight/brightness'"
        ),
    ), pytest.raises(
        PermissionError,
        match=re.escape(
            "Insufficient permissions to set brightness of backlight."
            "\nConsider adding the following udev rules:"
            '\nACTION=="add", SUBSYSTEM=="backlight", KERNEL=="intel_backlight"'
            ', RUN+="/bin/chgrp video /sys$devpath/brightness"'
            '\nACTION=="add", SUBSYSTEM=="backlight", KERNEL=="intel_backlight"'
            ', RUN+="/bin/chmod g+w /sys$devpath/brightness"'
        ),
    ):
        backlight._brightness_absolute = 42


@pytest.mark.parametrize(
    ("max_brightness", "brightness_absolute_str", "expected_brightness_relative"),
    (
        (100, "0", 0),
        (100, "100", 1),
        (100, "40", 0.4),
        (4096, "2048", 0.5),
        (4096, "3584", 0.875),
    ),
)
def test_brightness_relative_get(
    tmp_path: pathlib.Path,
    max_brightness,
    brightness_absolute_str,
    expected_brightness_relative,
):
    backlight = Backlight()
    backlight._acpi_dir_path = tmp_path
    tmp_path.joinpath("brightness").write_text(brightness_absolute_str)
    tmp_path.joinpath("max_brightness").write_text(str(max_brightness))
    assert backlight.brightness_relative == pytest.approx(expected_brightness_relative)


@pytest.mark.parametrize(
    ("max_brightness", "brightness_relative", "expected_brightness_abs_str"),
    (
        (100, 0, "0"),
        (100, 1, "100"),
        (100, 0.4, "40"),
        (4096, 0.5, "2048"),
        (4096, 0.8, "3277"),
        (100, 1.1, "100"),
        (100, -0.1, "0"),
    ),
)
def test_brightness_relative_set(
    tmp_path: pathlib.Path,
    max_brightness,
    brightness_relative,
    expected_brightness_abs_str,
):
    backlight = Backlight()
    backlight._acpi_dir_path = tmp_path
    tmp_path.joinpath("max_brightness").write_text(str(max_brightness))
    backlight.brightness_relative = brightness_relative
    assert tmp_path.joinpath("brightness").read_text() == expected_brightness_abs_str


@pytest.mark.parametrize(
    ("current_brightness_relative", "expr_str", "expected_new_brightness_relative"),
    (
        (100, "1", 1),
        (100, "0", 0),
        (100, "0.42", 0.42),
        (100, "1/4", 0.25),
        (100, "b * 0.5", 50),
        (100, "b / 2", 50),
        (100, "b + 21", 121),
        (100, "b - 21", 79),
    ),
)
def test_backlight_eval(
    current_brightness_relative, expr_str, expected_new_brightness_relative
):
    with unittest.mock.patch(
        "acpi_backlight.Backlight.brightness_relative",
        new_callable=unittest.mock.PropertyMock,
    ) as property_mock:
        property_mock.return_value = current_brightness_relative
        backlight_eval(expr_str)
    # args and kwargs properties were added in python3.8
    assert all(not call[1] for call in property_mock.call_args_list)
    setter_calls_args = [call[0] for call in property_mock.call_args_list if call[0]]
    assert len(setter_calls_args) == 1, setter_calls_args
    (new_brightness_relative,) = setter_calls_args[0]
    assert new_brightness_relative == pytest.approx(expected_new_brightness_relative)