"""Tests for AutomationStore — CRUD, rules, name uniqueness."""

import pytest

from wled_controller.storage.automation import (
    ApplicationRule,
    Automation,
    DisplayStateRule,
    MQTTRule,
    Rule,
    StartupRule,
    SystemIdleRule,
    TimeOfDayRule,
    WebhookRule,
)
from wled_controller.storage.automation_store import AutomationStore


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


# ---------------------------------------------------------------------------
# Rule models
# ---------------------------------------------------------------------------


class TestRuleModels:
    def test_application_round_trip(self):
        r = ApplicationRule(apps=["chrome.exe", "firefox.exe"], match_type="topmost")
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, ApplicationRule)
        assert restored.apps == ["chrome.exe", "firefox.exe"]
        assert restored.match_type == "topmost"

    def test_time_of_day_round_trip(self):
        r = TimeOfDayRule(start_time="22:00", end_time="06:00")
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, TimeOfDayRule)
        assert restored.start_time == "22:00"
        assert restored.end_time == "06:00"

    def test_system_idle_round_trip(self):
        r = SystemIdleRule(idle_minutes=10, when_idle=False)
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, SystemIdleRule)
        assert restored.idle_minutes == 10
        assert restored.when_idle is False

    def test_display_state_round_trip(self):
        r = DisplayStateRule(state="off")
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, DisplayStateRule)
        assert restored.state == "off"

    def test_mqtt_round_trip(self):
        r = MQTTRule(topic="home/tv", payload="on", match_mode="contains")
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, MQTTRule)
        assert restored.topic == "home/tv"
        assert restored.match_mode == "contains"

    def test_webhook_round_trip(self):
        r = WebhookRule(token="abc123")
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, WebhookRule)
        assert restored.token == "abc123"

    def test_startup_round_trip(self):
        r = StartupRule()
        data = r.to_dict()
        restored = Rule.from_dict(data)
        assert isinstance(restored, StartupRule)

    def test_unknown_rule_type_raises(self):
        with pytest.raises(ValueError, match="Unknown rule type"):
            Rule.from_dict({"rule_type": "nonexistent"})

    def test_legacy_condition_type_migration(self):
        """Legacy data with condition_type should still deserialize."""
        data = {"condition_type": "startup"}
        restored = Rule.from_dict(data)
        assert isinstance(restored, StartupRule)

    def test_legacy_always_maps_to_startup(self):
        """Legacy 'always' condition_type should map to StartupRule."""
        data = {"condition_type": "always"}
        restored = Rule.from_dict(data)
        assert isinstance(restored, StartupRule)


# ---------------------------------------------------------------------------
# Automation model
# ---------------------------------------------------------------------------


class TestAutomationModel:
    def test_round_trip(self, make_automation):
        auto = make_automation(
            name="Test Auto",
            rules=[StartupRule(), WebhookRule(token="tok1")],
            scene_preset_id="sp_123",
            deactivation_mode="revert",
        )
        data = auto.to_dict()
        restored = Automation.from_dict(data)

        assert restored.id == auto.id
        assert restored.name == "Test Auto"
        assert len(restored.rules) == 2
        assert isinstance(restored.rules[0], StartupRule)
        assert isinstance(restored.rules[1], WebhookRule)
        assert restored.scene_preset_id == "sp_123"
        assert restored.deactivation_mode == "revert"

    def test_from_dict_skips_unknown_rules(self):
        data = {
            "id": "a1",
            "name": "Skip",
            "enabled": True,
            "rule_logic": "or",
            "rules": [
                {"rule_type": "startup"},
                {"rule_type": "future_unknown"},
            ],
            "scene_preset_id": None,
            "deactivation_mode": "none",
            "deactivation_scene_preset_id": None,
            "created_at": "2025-01-01T00:00:00+00:00",
            "updated_at": "2025-01-01T00:00:00+00:00",
        }
        auto = Automation.from_dict(data)
        assert len(auto.rules) == 1  # unknown was skipped

    def test_legacy_conditions_field_migration(self):
        """Legacy data with 'conditions' and 'condition_logic' should migrate."""
        data = {
            "id": "a2",
            "name": "Legacy",
            "enabled": True,
            "condition_logic": "and",
            "conditions": [
                {"condition_type": "startup"},
                {"condition_type": "application", "apps": ["test.exe"], "match_type": "running"},
            ],
            "scene_preset_id": None,
            "deactivation_mode": "none",
            "deactivation_scene_preset_id": None,
            "created_at": "2025-01-01T00:00:00+00:00",
            "updated_at": "2025-01-01T00:00:00+00:00",
        }
        auto = Automation.from_dict(data)
        assert auto.rule_logic == "and"
        assert len(auto.rules) == 2
        assert isinstance(auto.rules[0], StartupRule)
        assert isinstance(auto.rules[1], ApplicationRule)


# ---------------------------------------------------------------------------
# AutomationStore CRUD
# ---------------------------------------------------------------------------


class TestAutomationStoreCRUD:
    def test_create(self, store):
        a = store.create_automation(name="Auto A")
        assert a.id.startswith("auto_")
        assert a.name == "Auto A"
        assert a.enabled is True
        assert a.rule_logic == "or"
        assert store.count() == 1

    def test_create_with_rules(self, store):
        rules = [
            StartupRule(),
            WebhookRule(token="secret123"),
        ]
        a = store.create_automation(
            name="Full",
            enabled=False,
            rule_logic="and",
            rules=rules,
            scene_preset_id="sp_001",
            deactivation_mode="fallback_scene",
            deactivation_scene_preset_id="sp_002",
            tags=["test"],
        )
        assert a.enabled is False
        assert a.rule_logic == "and"
        assert len(a.rules) == 2
        assert a.scene_preset_id == "sp_001"
        assert a.tags == ["test"]

    def test_get_all(self, store):
        store.create_automation("A")
        store.create_automation("B")
        assert len(store.get_all_automations()) == 2

    def test_get(self, store):
        created = store.create_automation("Get")
        got = store.get_automation(created.id)
        assert got.name == "Get"

    def test_delete(self, store):
        a = store.create_automation("Del")
        store.delete_automation(a.id)
        assert store.count() == 0

    def test_delete_not_found(self, store):
        with pytest.raises(ValueError, match="not found"):
            store.delete_automation("nope")

    def test_update(self, store):
        a = store.create_automation(name="Old", enabled=True)
        updated = store.update_automation(a.id, name="New", enabled=False)
        assert updated.name == "New"
        assert updated.enabled is False

    def test_update_rules(self, store):
        a = store.create_automation(name="Rules")
        new_rules = [ApplicationRule(apps=["notepad.exe"])]
        updated = store.update_automation(a.id, rules=new_rules)
        assert len(updated.rules) == 1
        assert isinstance(updated.rules[0], ApplicationRule)

    def test_update_scene_preset_id_clear(self, store):
        a = store.create_automation(name="SP", scene_preset_id="sp_1")
        updated = store.update_automation(a.id, scene_preset_id="")
        assert updated.scene_preset_id is None

    def test_update_partial(self, store):
        a = store.create_automation(name="Partial", enabled=True, tags=["orig"])
        updated = store.update_automation(a.id, tags=["new"])
        assert updated.name == "Partial"
        assert updated.enabled is True
        assert updated.tags == ["new"]


# ---------------------------------------------------------------------------
# Name uniqueness
# ---------------------------------------------------------------------------


class TestAutomationNameUniqueness:
    def test_duplicate_name_create(self, store):
        store.create_automation("Dup")
        with pytest.raises(ValueError, match="already exists"):
            store.create_automation("Dup")

    def test_duplicate_name_update(self, store):
        store.create_automation("First")
        a2 = store.create_automation("Second")
        with pytest.raises(ValueError, match="already exists"):
            store.update_automation(a2.id, name="First")


# ---------------------------------------------------------------------------
# Persistence
# ---------------------------------------------------------------------------


class TestAutomationPersistence:
    def test_persist_and_reload(self, tmp_path):
        from wled_controller.storage.database import Database

        db = Database(tmp_path / "auto_persist.db")
        s1 = AutomationStore(db)
        a = s1.create_automation(
            name="Persist",
            rules=[WebhookRule(token="t1")],
        )
        aid = a.id

        s2 = AutomationStore(db)
        loaded = s2.get_automation(aid)
        assert loaded.name == "Persist"
        assert len(loaded.rules) == 1
        assert isinstance(loaded.rules[0], WebhookRule)
        db.close()
