"""Tests for auto_brightness_daemon module."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from python_pkg.brightness_controller import auto_brightness_daemon

# ── _find_als_device ─────────────────────────────────────────────────────


class TestFindAlsDevice:
    """Tests for _find_als_device."""

    @patch.object(
        Path,
        "glob",
        return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
    )
    def test_found(self, mock_glob: MagicMock) -> None:
        result = auto_brightness_daemon._find_als_device()
        assert result == Path("/sys/bus/iio/devices/iio0")

    @patch.object(Path, "glob", return_value=[])
    def test_not_found(self, mock_glob: MagicMock) -> None:
        assert auto_brightness_daemon._find_als_device() is None


# ── _read_lux ────────────────────────────────────────────────────────────


class TestReadLux:
    """Tests for _read_lux."""

    def test_basic_read(self, tmp_path: Path) -> None:
        (tmp_path / "in_illuminance_raw").write_text("100\n")
        (tmp_path / "in_illuminance_scale").write_text("2.0\n")
        (tmp_path / "in_illuminance_offset").write_text("5.0\n")
        result = auto_brightness_daemon._read_lux(tmp_path)
        assert result == pytest.approx((100 + 5.0) * 2.0)

    def test_missing_scale(self, tmp_path: Path) -> None:
        (tmp_path / "in_illuminance_raw").write_text("50\n")
        # No scale file → default 1.0
        (tmp_path / "in_illuminance_offset").write_text("0\n")
        result = auto_brightness_daemon._read_lux(tmp_path)
        assert result == pytest.approx(50.0)

    def test_missing_offset(self, tmp_path: Path) -> None:
        (tmp_path / "in_illuminance_raw").write_text("50\n")
        (tmp_path / "in_illuminance_scale").write_text("1.0\n")
        # No offset file → default 0.0
        result = auto_brightness_daemon._read_lux(tmp_path)
        assert result == pytest.approx(50.0)

    def test_invalid_scale_value(self, tmp_path: Path) -> None:
        (tmp_path / "in_illuminance_raw").write_text("50\n")
        (tmp_path / "in_illuminance_scale").write_text("bad\n")
        (tmp_path / "in_illuminance_offset").write_text("0\n")
        result = auto_brightness_daemon._read_lux(tmp_path)
        assert result == pytest.approx(50.0)

    def test_invalid_offset_value(self, tmp_path: Path) -> None:
        (tmp_path / "in_illuminance_raw").write_text("50\n")
        (tmp_path / "in_illuminance_scale").write_text("1.0\n")
        (tmp_path / "in_illuminance_offset").write_text("bad\n")
        result = auto_brightness_daemon._read_lux(tmp_path)
        assert result == pytest.approx(50.0)


# ── _lux_to_brightness ──────────────────────────────────────────────────


class TestLuxToBrightness:
    """Tests for _lux_to_brightness."""

    def test_below_minimum(self) -> None:
        assert auto_brightness_daemon._lux_to_brightness(-10.0) == 10

    def test_at_minimum(self) -> None:
        assert auto_brightness_daemon._lux_to_brightness(0.0) == 10

    def test_above_maximum(self) -> None:
        assert auto_brightness_daemon._lux_to_brightness(10000.0) == 100

    def test_at_maximum(self) -> None:
        assert auto_brightness_daemon._lux_to_brightness(5000.0) == 100

    def test_interpolation_mid(self) -> None:
        result = auto_brightness_daemon._lux_to_brightness(27.5)
        assert result == 57

    def test_interpolation_first_segment(self) -> None:
        result = auto_brightness_daemon._lux_to_brightness(2.5)
        assert result == 25

    def test_fallback_return(self) -> None:
        """Exercise the post-loop fallback (unreachable with monotonic curves)."""
        nan = float("nan")
        with patch.object(
            auto_brightness_daemon,
            "LUX_CURVE",
            [(nan, 10), (nan, 99)],
        ):
            assert auto_brightness_daemon._lux_to_brightness(50.0) == 99


# ── _get_brightness ──────────────────────────────────────────────────────


class TestGetBrightness:
    """Tests for _get_brightness."""

    @patch("python_pkg.brightness_controller.auto_brightness_daemon.subprocess.run")
    def test_valid_output(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(
            stdout="intel_backlight,backlight,50,42%,120000"
        )
        assert auto_brightness_daemon._get_brightness() == 42

    @patch("python_pkg.brightness_controller.auto_brightness_daemon.subprocess.run")
    def test_no_backlight_device(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(stdout="kbd_backlight,leds,0,0%,3")
        assert auto_brightness_daemon._get_brightness() == -1

    @patch("python_pkg.brightness_controller.auto_brightness_daemon.subprocess.run")
    def test_too_few_fields(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(stdout="a,b,c")
        assert auto_brightness_daemon._get_brightness() == -1

    @patch("python_pkg.brightness_controller.auto_brightness_daemon.subprocess.run")
    def test_empty_output(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(stdout="")
        assert auto_brightness_daemon._get_brightness() == -1


# ── _set_brightness ──────────────────────────────────────────────────────


class TestSetBrightness:
    """Tests for _set_brightness."""

    @patch("python_pkg.brightness_controller.auto_brightness_daemon.subprocess.run")
    def test_calls_brightnessctl(self, mock_run: MagicMock) -> None:
        auto_brightness_daemon._set_brightness(75)
        mock_run.assert_called_once_with(
            [auto_brightness_daemon._BRIGHTNESSCTL, "-q", "set", "75%"],
            check=False,
        )


# ── _is_enabled ──────────────────────────────────────────────────────────


class TestIsEnabled:
    """Tests for _is_enabled."""

    def test_enabled(self, tmp_path: Path) -> None:
        enabled_file = tmp_path / "enabled"
        enabled_file.write_text("1\n")
        with patch.object(auto_brightness_daemon, "ENABLED_FILE", enabled_file):
            assert auto_brightness_daemon._is_enabled() is True

    def test_disabled(self, tmp_path: Path) -> None:
        enabled_file = tmp_path / "enabled"
        enabled_file.write_text("0\n")
        with patch.object(auto_brightness_daemon, "ENABLED_FILE", enabled_file):
            assert auto_brightness_daemon._is_enabled() is False

    def test_missing_file(self, tmp_path: Path) -> None:
        enabled_file = tmp_path / "nonexistent"
        with patch.object(auto_brightness_daemon, "ENABLED_FILE", enabled_file):
            assert auto_brightness_daemon._is_enabled() is False


# ── _set_enabled ─────────────────────────────────────────────────────────


class TestSetEnabled:
    """Tests for _set_enabled."""

    def test_enable(self, tmp_path: Path) -> None:
        config_dir = tmp_path / "config"
        enabled_file = config_dir / "enabled"
        with (
            patch.object(auto_brightness_daemon, "CONFIG_DIR", config_dir),
            patch.object(auto_brightness_daemon, "ENABLED_FILE", enabled_file),
        ):
            auto_brightness_daemon._set_enabled(enabled=True)
            assert enabled_file.read_text() == "1"

    def test_disable(self, tmp_path: Path) -> None:
        config_dir = tmp_path / "config"
        enabled_file = config_dir / "enabled"
        with (
            patch.object(auto_brightness_daemon, "CONFIG_DIR", config_dir),
            patch.object(auto_brightness_daemon, "ENABLED_FILE", enabled_file),
        ):
            auto_brightness_daemon._set_enabled(enabled=False)
            assert enabled_file.read_text() == "0"


# ── _clamp ───────────────────────────────────────────────────────────────


class TestClamp:
    """Tests for _clamp."""

    def test_within_range(self) -> None:
        assert auto_brightness_daemon._clamp(5, 0, 10) == 5

    def test_below_low(self) -> None:
        assert auto_brightness_daemon._clamp(-5, 0, 10) == 0

    def test_above_high(self) -> None:
        assert auto_brightness_daemon._clamp(15, 0, 10) == 10


# ── main ─────────────────────────────────────────────────────────────────
