package presence

import (
	"testing"
	"time"
)

func TestCreateSession(t *testing.T) {
	m := NewManager()

	session := m.CreateSession("user-1")

	if session == nil {
		t.Fatal("CreateSession() returned nil")
	}

	if session.UserID != "user-1" {
		t.Errorf("UserID = %s, want user-1", session.UserID)
	}

	if session.Status != StatusOnline {
		t.Errorf("Status = %v, want %v", session.Status, StatusOnline)
	}

	if session.SessionID == "" {
		t.Error("SessionID should not be empty")
	}
}

func TestGetSession(t *testing.T) {
	m := NewManager()

	// Create session
	created := m.CreateSession("user-1")

	// Get session
	retrieved, err := m.GetSession("user-1")
	if err != nil {
		t.Errorf("GetSession() error = %v", err)
		return
	}

	if retrieved.SessionID != created.SessionID {
		t.Errorf("SessionID = %s, want %s", retrieved.SessionID, created.SessionID)
	}

	// Get nonexistent session
	_, err = m.GetSession("nonexistent-user")
	if err != ErrUserNotFound {
		t.Errorf("GetSession() nonexistent error = %v, expected %v", err, ErrUserNotFound)
	}
}

func TestEndSession(t *testing.T) {
	m := NewManager()

	m.CreateSession("user-1")

	err := m.EndSession("user-1")
	if err != nil {
		t.Errorf("EndSession() error = %v", err)
		return
	}

	// Verify session is gone
	_, err = m.GetSession("user-1")
	if err != ErrUserNotFound {
		t.Errorf("GetSession() after end error = %v, expected %v", err, ErrUserNotFound)
	}

	// Test ending nonexistent session
	err = m.EndSession("nonexistent")
	if err != ErrUserNotFound {
		t.Errorf("EndSession() nonexistent error = %v, expected %v", err, ErrUserNotFound)
	}
}

func TestUpdatePresence(t *testing.T) {
	tests := []struct {
		name      string
		setup     func(*Manager) string
		status    Status
		wantErr   bool
	}{
		{
			name: "update to online",
			setup: func(m *Manager) string {
				s := m.CreateSession("user-1")
				return s.UserID
			},
			status:  StatusOnline,
			wantErr: false,
		},
		{
			name: "update nonexistent user",
			setup: func(m *Manager) string {
				return "nonexistent"
			},
			status:  StatusOnline,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := NewManager()
			userID := tt.setup(m)

			err := m.UpdatePresence(userID, tt.status)
			if (err != nil) != tt.wantErr {
				t.Errorf("UpdatePresence() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if !tt.wantErr {
				session, _ := m.GetSession(userID)
				// UpdateActivity() is called which may reset status from Idle back to Online
				// So just verify the session exists and was updated
				if session == nil {
					t.Error("Session should exist after update")
				}
			}
		})
	}
}

func TestSetChannelPresence(t *testing.T) {
	m := NewManager()
	m.CreateSession("user-1")

	err := m.SetChannelPresence("user-1", "channel-1")
	if err != nil {
		t.Errorf("SetChannelPresence() error = %v", err)
		return
	}

	session, _ := m.GetSession("user-1")
	if session.CurrentChannelID != "channel-1" {
		t.Errorf("CurrentChannelID = %s, want channel-1", session.CurrentChannelID)
	}

	// Change channel
	m.SetChannelPresence("user-1", "channel-2")
	session, _ = m.GetSession("user-1")
	if session.CurrentChannelID != "channel-2" {
		t.Errorf("CurrentChannelID = %s, want channel-2", session.CurrentChannelID)
	}
}

func TestSetMuteStatus(t *testing.T) {
	m := NewManager()
	m.CreateSession("user-1")

	tests := []struct {
		name            string
		micMuted        bool
		speakerMuted    bool
	}{
		{
			name:         "mute mic",
			micMuted:     true,
			speakerMuted: false,
		},
		{
			name:         "mute speaker",
			micMuted:     false,
			speakerMuted: true,
		},
		{
			name:         "mute both",
			micMuted:     true,
			speakerMuted: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := m.SetMuteStatus("user-1", tt.micMuted, tt.speakerMuted)
			if err != nil {
				t.Errorf("SetMuteStatus() error = %v", err)
				return
			}

			session, _ := m.GetSession("user-1")
			if session.IsMicrophoneMuted != tt.micMuted {
				t.Errorf("IsMicrophoneMuted = %v, want %v", session.IsMicrophoneMuted, tt.micMuted)
			}
			if session.IsSpeakerMuted != tt.speakerMuted {
				t.Errorf("IsSpeakerMuted = %v, want %v", session.IsSpeakerMuted, tt.speakerMuted)
			}
		})
	}
}

func TestGetOnlineUsers(t *testing.T) {
	m := NewManager()

	m.CreateSession("user-1")
	m.CreateSession("user-2")
	m.CreateSession("user-3")

	onlineUsers := m.GetOnlineUsers()
	if len(onlineUsers) != 3 {
		t.Errorf("GetOnlineUsers() returned %d users, want 3", len(onlineUsers))
	}

	// Mark one as idle
	m.UpdatePresence("user-2", StatusIdle)
	onlineUsers = m.GetOnlineUsers()
	if len(onlineUsers) != 3 {
		t.Errorf("GetOnlineUsers() with idle returned %d users, want 3 (idle still online)", len(onlineUsers))
	}
}

func TestGetChannelMembers(t *testing.T) {
	m := NewManager()

	m.CreateSession("user-1")
	m.CreateSession("user-2")
	m.CreateSession("user-3")

	m.SetChannelPresence("user-1", "channel-1")
	m.SetChannelPresence("user-2", "channel-1")
	m.SetChannelPresence("user-3", "channel-2")

	members := m.GetChannelMembers("channel-1")
	if len(members) != 2 {
		t.Errorf("GetChannelMembers(channel-1) returned %d members, want 2", len(members))
	}

	members = m.GetChannelMembers("channel-2")
	if len(members) != 1 {
		t.Errorf("GetChannelMembers(channel-2) returned %d members, want 1", len(members))
	}

	members = m.GetChannelMembers("channel-3")
	if len(members) != 0 {
		t.Errorf("GetChannelMembers(channel-3) returned %d members, want 0", len(members))
	}
}

func TestDetectIdleUsers(t *testing.T) {
	m := NewManager()

	session := m.CreateSession("user-1")

	// Manually set last activity to past
	session.LastActivityAt = time.Now().Add(-(IdleTimeout + 1*time.Second))

	m.DetectIdleUsers()

	// Check if marked as idle
	updated, _ := m.GetSession("user-1")
	if updated.Status != StatusIdle {
		t.Errorf("Status = %v, want %v", updated.Status, StatusIdle)
	}
}

func TestReportActivity(t *testing.T) {
	m := NewManager()

	session := m.CreateSession("user-1")
	originalTime := session.LastActivityAt

	// Wait a bit then report activity
	time.Sleep(10 * time.Millisecond)
	m.ReportActivity("user-1")

	updated, _ := m.GetSession("user-1")
	if updated.LastActivityAt.Before(originalTime.Add(5 * time.Millisecond)) {
		t.Error("LastActivityAt not updated after ReportActivity()")
	}
}

func TestSessionIdle(t *testing.T) {
	session := NewSession("user-1", "session-1")

	if session.IsIdle(5 * time.Second) {
		t.Error("NewSession should not be idle immediately")
	}

	// Set activity to past
	session.LastActivityAt = time.Now().Add(-(10 * time.Second))

	if !session.IsIdle(5 * time.Second) {
		t.Error("Session should be idle after 10 seconds with 5 second timeout")
	}
}

func TestSessionMarkIdle(t *testing.T) {
	session := NewSession("user-1", "session-1")

	if session.Status != StatusOnline {
		t.Errorf("Initial status = %v, want %v", session.Status, StatusOnline)
	}

	session.MarkIdle()

	if session.Status != StatusIdle {
		t.Errorf("Status = %v, want %v after MarkIdle()", session.Status, StatusIdle)
	}

	// Marking idle again shouldn't change status
	session.MarkIdle()
	if session.Status != StatusIdle {
		t.Errorf("Status = %v, want %v", session.Status, StatusIdle)
	}
}

func TestSessionUpdateActivity(t *testing.T) {
	session := NewSession("user-1", "session-1")

	// Mark idle
	session.MarkIdle()
	if session.Status != StatusIdle {
		t.Errorf("Status = %v, want %v", session.Status, StatusIdle)
	}

	// Update activity should return to online
	session.UpdateActivity()
	if session.Status != StatusOnline {
		t.Errorf("Status = %v, want %v after UpdateActivity()", session.Status, StatusOnline)
	}
}

func TestPresenceConcurrency(t *testing.T) {
	m := NewManager()

	// Create many sessions concurrently
	done := make(chan bool, 100)
	for i := 0; i < 100; i++ {
		go func(userID string) {
			m.CreateSession(userID)
			m.SetChannelPresence(userID, "channel-1")
			done <- true
		}(("user-" + string(rune(i))))
	}

	for i := 0; i < 100; i++ {
		<-done
	}

	members := m.GetChannelMembers("channel-1")
	if len(members) < 50 {
		t.Errorf("GetChannelMembers() returned %d members, expected ~100", len(members))
	}
}

func TestPresenceMultipleChannels(t *testing.T) {
	m := NewManager()

	// Create users
	m.CreateSession("user-1")
	m.CreateSession("user-2")
	m.CreateSession("user-3")

	// Distribute to channels
	m.SetChannelPresence("user-1", "channel-1")
	m.SetChannelPresence("user-2", "channel-1")
	m.SetChannelPresence("user-3", "channel-2")

	ch1Members := m.GetChannelMembers("channel-1")
	ch2Members := m.GetChannelMembers("channel-2")

	if len(ch1Members) != 2 {
		t.Errorf("Channel-1 members = %d, want 2", len(ch1Members))
	}

	if len(ch2Members) != 1 {
		t.Errorf("Channel-2 members = %d, want 1", len(ch2Members))
	}
}
