package loginctl

import (
	"sync"
	"testing"

	"github.com/godbus/dbus/v5"
	"github.com/stretchr/testify/assert"
)

func TestManager_HandleDBusSignal_Lock(t *testing.T) {
	manager := &Manager{
		state: &SessionState{
			Locked:     false,
			LockedHint: false,
		},
		stateMutex:  sync.RWMutex{},
		subscribers: make(map[string]chan SessionState),
		subMutex:    sync.RWMutex{},
		dirty:       make(chan struct{}, 1),
	}

	sig := &dbus.Signal{
		Name: "org.freedesktop.login1.Session.Lock",
	}

	manager.handleDBusSignal(sig)

	manager.stateMutex.RLock()
	defer manager.stateMutex.RUnlock()
	assert.True(t, manager.state.Locked)
	assert.True(t, manager.state.LockedHint)
}

func TestManager_HandleDBusSignal_Unlock(t *testing.T) {
	manager := &Manager{
		state: &SessionState{
			Locked:     true,
			LockedHint: true,
		},
		stateMutex:  sync.RWMutex{},
		subscribers: make(map[string]chan SessionState),
		subMutex:    sync.RWMutex{},
		dirty:       make(chan struct{}, 1),
	}

	sig := &dbus.Signal{
		Name: "org.freedesktop.login1.Session.Unlock",
	}

	manager.handleDBusSignal(sig)

	manager.stateMutex.RLock()
	defer manager.stateMutex.RUnlock()
	assert.False(t, manager.state.Locked)
	assert.False(t, manager.state.LockedHint)
}

func TestManager_HandleDBusSignal_PrepareForSleep(t *testing.T) {
	t.Run("preparing for sleep - true", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				PreparingForSleep: false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.login1.Manager.PrepareForSleep",
			Body: []interface{}{true},
		}

		manager.handleDBusSignal(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.True(t, manager.state.PreparingForSleep)
	})

	t.Run("preparing for sleep - false", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				PreparingForSleep: true,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.login1.Manager.PrepareForSleep",
			Body: []interface{}{false},
		}

		manager.handleDBusSignal(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.False(t, manager.state.PreparingForSleep)
	})

	t.Run("empty body", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				PreparingForSleep: false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.login1.Manager.PrepareForSleep",
			Body: []interface{}{},
		}

		manager.handleDBusSignal(sig)

		// State should remain unchanged
		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.False(t, manager.state.PreparingForSleep)
	})
}

func TestManager_HandlePropertiesChanged(t *testing.T) {
	t.Run("active property changed", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				Active: false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{
				"org.freedesktop.login1.Session",
				map[string]dbus.Variant{
					"Active": dbus.MakeVariant(true),
				},
			},
		}

		manager.handlePropertiesChanged(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.True(t, manager.state.Active)
	})

	t.Run("idle hint property changed", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				IdleHint: false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{
				"org.freedesktop.login1.Session",
				map[string]dbus.Variant{
					"IdleHint": dbus.MakeVariant(true),
				},
			},
		}

		manager.handlePropertiesChanged(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.True(t, manager.state.IdleHint)
	})

	t.Run("idle since hint property changed", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				IdleSinceHint: 0,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{
				"org.freedesktop.login1.Session",
				map[string]dbus.Variant{
					"IdleSinceHint": dbus.MakeVariant(uint64(123456789)),
				},
			},
		}

		manager.handlePropertiesChanged(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.Equal(t, uint64(123456789), manager.state.IdleSinceHint)
	})

	t.Run("locked hint property changed", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				LockedHint: false,
				Locked:     false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{
				"org.freedesktop.login1.Session",
				map[string]dbus.Variant{
					"LockedHint": dbus.MakeVariant(true),
				},
			},
		}

		manager.handlePropertiesChanged(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.True(t, manager.state.LockedHint)
		assert.True(t, manager.state.Locked)
	})

	t.Run("wrong interface", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				Active: false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{
				"org.freedesktop.SomeOtherInterface",
				map[string]dbus.Variant{
					"Active": dbus.MakeVariant(true),
				},
			},
		}

		manager.handlePropertiesChanged(sig)

		// State should remain unchanged
		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.False(t, manager.state.Active)
	})

	t.Run("empty body", func(t *testing.T) {
		manager := &Manager{
			state:       &SessionState{},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{},
		}

		// Should not panic
		assert.NotPanics(t, func() {
			manager.handlePropertiesChanged(sig)
		})
	})

	t.Run("multiple properties changed", func(t *testing.T) {
		manager := &Manager{
			state: &SessionState{
				Active:   false,
				IdleHint: false,
			},
			stateMutex:  sync.RWMutex{},
			subscribers: make(map[string]chan SessionState),
			subMutex:    sync.RWMutex{},
			dirty:       make(chan struct{}, 1),
		}

		sig := &dbus.Signal{
			Name: "org.freedesktop.DBus.Properties.PropertiesChanged",
			Body: []interface{}{
				"org.freedesktop.login1.Session",
				map[string]dbus.Variant{
					"Active":   dbus.MakeVariant(true),
					"IdleHint": dbus.MakeVariant(true),
				},
			},
		}

		manager.handlePropertiesChanged(sig)

		manager.stateMutex.RLock()
		defer manager.stateMutex.RUnlock()
		assert.True(t, manager.state.Active)
		assert.True(t, manager.state.IdleHint)
	})
}
