"""Tests for AutomationEngine — condition evaluation in isolation."""

from datetime import datetime, timezone
from unittest.mock import MagicMock, patch

import pytest

from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.storage.automation import (
    AlwaysCondition,
    ApplicationCondition,
    Automation,
    DisplayStateCondition,
    StartupCondition,
    SystemIdleCondition,
    TimeOfDayCondition,
    WebhookCondition,
)
from wled_controller.storage.automation_store import AutomationStore


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def mock_store(tmp_db) -> AutomationStore:
    return AutomationStore(tmp_db)


@pytest.fixture
def mock_manager():
    m = MagicMock()
    m.fire_event = MagicMock()
    return m


@pytest.fixture
def engine(mock_store, mock_manager) -> AutomationEngine:
    """Build an AutomationEngine with the PlatformDetector mocked out.

    PlatformDetector starts a Windows display-power listener thread that
    causes access violations in the test environment, so we replace it
    with a simple MagicMock.
    """
    with patch(
        "wled_controller.core.automations.automation_engine.PlatformDetector"
    ):
        eng = AutomationEngine(
            automation_store=mock_store,
            processor_manager=mock_manager,
            poll_interval=0.1,
        )
    return eng


# ---------------------------------------------------------------------------
# Condition evaluation (unit-level)
# ---------------------------------------------------------------------------


class TestConditionEvaluation:
    """Test _evaluate_condition for each condition type individually."""

    def _make_automation(self, conditions):
        now = datetime.now(timezone.utc)
        return Automation(
            id="test_auto",
            name="Test",
            enabled=True,
            condition_logic="or",
            conditions=conditions,
            scene_preset_id=None,
            deactivation_mode="none",
            deactivation_scene_preset_id=None,
            created_at=now,
            updated_at=now,
        )

    def _eval(self, engine, condition, **kwargs):
        """Invoke the private _evaluate_condition method."""
        defaults = dict(
            running_procs=set(),
            topmost_proc=None,
            topmost_fullscreen=False,
            fullscreen_procs=set(),
            idle_seconds=None,
            display_state=None,
        )
        defaults.update(kwargs)
        return engine._evaluate_condition(
            condition,
            defaults["running_procs"],
            defaults["topmost_proc"],
            defaults["topmost_fullscreen"],
            defaults["fullscreen_procs"],
            defaults["idle_seconds"],
            defaults["display_state"],
        )

    def test_always_true(self, engine):
        assert self._eval(engine, AlwaysCondition()) is True

    def test_startup_true(self, engine):
        assert self._eval(engine, StartupCondition()) is True

    def test_application_running_match(self, engine):
        cond = ApplicationCondition(apps=["chrome.exe"], match_type="running")
        result = self._eval(
            engine, cond,
            running_procs={"chrome.exe", "explorer.exe"},
        )
        assert result is True

    def test_application_running_no_match(self, engine):
        cond = ApplicationCondition(apps=["chrome.exe"], match_type="running")
        result = self._eval(
            engine, cond,
            running_procs={"explorer.exe"},
        )
        assert result is False

    def test_application_topmost_match(self, engine):
        cond = ApplicationCondition(apps=["game.exe"], match_type="topmost")
        result = self._eval(
            engine, cond,
            topmost_proc="game.exe",
        )
        assert result is True

    def test_application_topmost_no_match(self, engine):
        cond = ApplicationCondition(apps=["game.exe"], match_type="topmost")
        result = self._eval(
            engine, cond,
            topmost_proc="chrome.exe",
        )
        assert result is False

    def test_time_of_day_within_range(self, engine):
        cond = TimeOfDayCondition(start_time="00:00", end_time="23:59")
        result = self._eval(engine, cond)
        assert result is True

    def test_system_idle_when_idle(self, engine):
        cond = SystemIdleCondition(idle_minutes=5, when_idle=True)
        result = self._eval(engine, cond, idle_seconds=600.0)  # 10 minutes idle
        assert result is True

    def test_system_idle_not_idle(self, engine):
        cond = SystemIdleCondition(idle_minutes=5, when_idle=True)
        result = self._eval(engine, cond, idle_seconds=60.0)  # 1 minute idle
        assert result is False

    def test_system_idle_when_not_idle(self, engine):
        """when_idle=False means active when user is NOT idle."""
        cond = SystemIdleCondition(idle_minutes=5, when_idle=False)
        result = self._eval(engine, cond, idle_seconds=60.0)  # 1 min idle (not yet 5)
        assert result is True

    def test_display_state_match(self, engine):
        cond = DisplayStateCondition(state="on")
        result = self._eval(engine, cond, display_state="on")
        assert result is True

    def test_display_state_no_match(self, engine):
        cond = DisplayStateCondition(state="off")
        result = self._eval(engine, cond, display_state="on")
        assert result is False

    def test_webhook_active(self, engine):
        cond = WebhookCondition(token="tok123")
        engine._webhook_states["tok123"] = True
        result = self._eval(engine, cond)
        assert result is True

    def test_webhook_inactive(self, engine):
        cond = WebhookCondition(token="tok123")
        # Not in _webhook_states → False
        result = self._eval(engine, cond)
        assert result is False


# ---------------------------------------------------------------------------
# Condition logic (AND / OR)
# ---------------------------------------------------------------------------


class TestConditionLogic:
    def _make_automation(self, conditions, logic="or"):
        now = datetime.now(timezone.utc)
        return Automation(
            id="logic_auto",
            name="Logic",
            enabled=True,
            condition_logic=logic,
            conditions=conditions,
            scene_preset_id=None,
            deactivation_mode="none",
            deactivation_scene_preset_id=None,
            created_at=now,
            updated_at=now,
        )

    def test_or_any_true(self, engine):
        auto = self._make_automation(
            [
                ApplicationCondition(apps=["missing.exe"], match_type="running"),
                AlwaysCondition(),
            ],
            logic="or",
        )
        result = engine._evaluate_conditions(
            auto,
            running_procs=set(),
            topmost_proc=None,
            topmost_fullscreen=False,
            fullscreen_procs=set(),
            idle_seconds=None,
            display_state=None,
        )
        assert result is True

    def test_and_all_must_be_true(self, engine):
        auto = self._make_automation(
            [
                AlwaysCondition(),
                ApplicationCondition(apps=["missing.exe"], match_type="running"),
            ],
            logic="and",
        )
        result = engine._evaluate_conditions(
            auto,
            running_procs=set(),
            topmost_proc=None,
            topmost_fullscreen=False,
            fullscreen_procs=set(),
            idle_seconds=None,
            display_state=None,
        )
        assert result is False


# ---------------------------------------------------------------------------
# Webhook state management
# ---------------------------------------------------------------------------


class TestWebhookState:
    @pytest.mark.asyncio
    async def test_set_webhook_state_activate(self, engine):
        await engine.set_webhook_state("tok_1", True)
        assert engine._webhook_states["tok_1"] is True

    @pytest.mark.asyncio
    async def test_set_webhook_state_deactivate(self, engine):
        engine._webhook_states["tok_1"] = True
        await engine.set_webhook_state("tok_1", False)
        assert engine._webhook_states["tok_1"] is False


# ---------------------------------------------------------------------------
# Start / Stop lifecycle
# ---------------------------------------------------------------------------


class TestEngineLifecycle:
    @pytest.mark.asyncio
    async def test_start_creates_task(self, engine):
        await engine.start()
        assert engine._task is not None
        await engine.stop()

    @pytest.mark.asyncio
    async def test_stop_cancels_task(self, engine):
        await engine.start()
        await engine.stop()
        assert engine._task is None

    @pytest.mark.asyncio
    async def test_double_start_is_safe(self, engine):
        await engine.start()
        await engine.start()  # no-op
        await engine.stop()

    @pytest.mark.asyncio
    async def test_stop_without_start_is_safe(self, engine):
        await engine.stop()  # no-op
