package channelserver

import (
	"sync"
	"testing"
)

func TestObjectStruct(t *testing.T) {
	obj := &Object{
		id:          12345,
		ownerCharID: 67890,
		x:           100.5,
		y:           50.25,
		z:           -10.0,
	}

	if obj.id != 12345 {
		t.Errorf("Object id = %d, want 12345", obj.id)
	}
	if obj.ownerCharID != 67890 {
		t.Errorf("Object ownerCharID = %d, want 67890", obj.ownerCharID)
	}
	if obj.x != 100.5 {
		t.Errorf("Object x = %f, want 100.5", obj.x)
	}
	if obj.y != 50.25 {
		t.Errorf("Object y = %f, want 50.25", obj.y)
	}
	if obj.z != -10.0 {
		t.Errorf("Object z = %f, want -10.0", obj.z)
	}
}

func TestObjectRWMutex(t *testing.T) {
	obj := &Object{
		id:          1,
		ownerCharID: 100,
		x:           0,
		y:           0,
		z:           0,
	}

	// Test read lock
	obj.RLock()
	_ = obj.x
	obj.RUnlock()

	// Test write lock
	obj.Lock()
	obj.x = 100.0
	obj.Unlock()

	if obj.x != 100.0 {
		t.Errorf("Object x = %f, want 100.0 after write", obj.x)
	}
}

func TestObjectConcurrentAccess(t *testing.T) {
	obj := &Object{
		id:          1,
		ownerCharID: 100,
		x:           0,
		y:           0,
		z:           0,
	}

	var wg sync.WaitGroup

	// Concurrent writers
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(val float32) {
			defer wg.Done()
			for j := 0; j < 100; j++ {
				obj.Lock()
				obj.x = val
				obj.y = val
				obj.z = val
				obj.Unlock()
			}
		}(float32(i))
	}

	// Concurrent readers
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 100; j++ {
				obj.RLock()
				_ = obj.x
				_ = obj.y
				_ = obj.z
				obj.RUnlock()
			}
		}()
	}

	wg.Wait()
}

func TestStageBinaryKeyStruct(t *testing.T) {
	key1 := stageBinaryKey{id0: 1, id1: 2}
	key2 := stageBinaryKey{id0: 1, id1: 3}
	key3 := stageBinaryKey{id0: 1, id1: 2}

	// Different keys
	if key1 == key2 {
		t.Error("key1 and key2 should be different")
	}

	// Same keys
	if key1 != key3 {
		t.Error("key1 and key3 should be equal")
	}
}

func TestStageBinaryKeyAsMapKey(t *testing.T) {
	data := make(map[stageBinaryKey][]byte)

	key1 := stageBinaryKey{id0: 0, id1: 0}
	key2 := stageBinaryKey{id0: 0, id1: 1}
	key3 := stageBinaryKey{id0: 1, id1: 0}

	data[key1] = []byte{0x01}
	data[key2] = []byte{0x02}
	data[key3] = []byte{0x03}

	if len(data) != 3 {
		t.Errorf("Expected 3 entries, got %d", len(data))
	}

	if data[key1][0] != 0x01 {
		t.Errorf("data[key1] = 0x%02X, want 0x01", data[key1][0])
	}
	if data[key2][0] != 0x02 {
		t.Errorf("data[key2] = 0x%02X, want 0x02", data[key2][0])
	}
	if data[key3][0] != 0x03 {
		t.Errorf("data[key3] = 0x%02X, want 0x03", data[key3][0])
	}
}

func TestNewStageDefaults(t *testing.T) {
	stage := NewStage("test_stage_001")

	if stage.id != "test_stage_001" {
		t.Errorf("stage.id = %s, want test_stage_001", stage.id)
	}
	if stage.maxPlayers != 127 {
		t.Errorf("stage.maxPlayers = %d, want 127 (default)", stage.maxPlayers)
	}
	if stage.objectIndex != 0 {
		t.Errorf("stage.objectIndex = %d, want 0", stage.objectIndex)
	}
	if stage.clients == nil {
		t.Error("stage.clients should be initialized")
	}
	if stage.reservedClientSlots == nil {
		t.Error("stage.reservedClientSlots should be initialized")
	}
	if stage.objects == nil {
		t.Error("stage.objects should be initialized")
	}
	if stage.rawBinaryData == nil {
		t.Error("stage.rawBinaryData should be initialized")
	}
	if stage.host != nil {
		t.Error("stage.host should be nil initially")
	}
	if stage.password != "" {
		t.Errorf("stage.password should be empty, got %s", stage.password)
	}
}

func TestStageReservedClientSlots(t *testing.T) {
	stage := NewStage("test")

	// Reserve some slots
	stage.reservedClientSlots[100] = true
	stage.reservedClientSlots[200] = false // ready status doesn't matter for presence
	stage.reservedClientSlots[300] = true

	if len(stage.reservedClientSlots) != 3 {
		t.Errorf("reservedClientSlots count = %d, want 3", len(stage.reservedClientSlots))
	}

	// Check ready status
	if !stage.reservedClientSlots[100] {
		t.Error("charID 100 should be ready")
	}
	if stage.reservedClientSlots[200] {
		t.Error("charID 200 should not be ready")
	}
}

func TestStageRawBinaryData(t *testing.T) {
	stage := NewStage("test")

	key := stageBinaryKey{id0: 5, id1: 10}
	data := []byte{0xDE, 0xAD, 0xBE, 0xEF}

	stage.rawBinaryData[key] = data

	retrieved := stage.rawBinaryData[key]
	if len(retrieved) != 4 {
		t.Fatalf("retrieved data len = %d, want 4", len(retrieved))
	}
	if retrieved[0] != 0xDE || retrieved[3] != 0xEF {
		t.Error("retrieved data doesn't match stored data")
	}
}

func TestStageObjects(t *testing.T) {
	stage := NewStage("test")

	obj := &Object{
		id:          1,
		ownerCharID: 12345,
		x:           100.0,
		y:           200.0,
		z:           300.0,
	}

	stage.objects[obj.id] = obj

	if len(stage.objects) != 1 {
		t.Errorf("objects count = %d, want 1", len(stage.objects))
	}

	retrieved := stage.objects[obj.id]
	if retrieved.ownerCharID != 12345 {
		t.Errorf("retrieved object ownerCharID = %d, want 12345", retrieved.ownerCharID)
	}
}

func TestStageHost(t *testing.T) {
	server := createMockServer()
	stage := NewStage("test")

	// Set host
	host := createMockSession(100, server)
	stage.host = host

	if stage.host != host {
		t.Error("stage host not set correctly")
	}
	if stage.host.charID != 100 {
		t.Errorf("stage host charID = %d, want 100", stage.host.charID)
	}
}

func TestStagePassword(t *testing.T) {
	stage := NewStage("test")

	// Set password
	stage.password = "secret123"

	if stage.password != "secret123" {
		t.Errorf("stage password = %s, want secret123", stage.password)
	}
}

func TestStageMaxPlayers(t *testing.T) {
	stage := NewStage("test")

	// Change max players
	stage.maxPlayers = 16

	if stage.maxPlayers != 16 {
		t.Errorf("stage maxPlayers = %d, want 16", stage.maxPlayers)
	}
}

func TestStageConcurrentClientAccess(t *testing.T) {
	server := createMockServer()
	stage := NewStage("test")

	var wg sync.WaitGroup

	// Concurrent client additions
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 10; j++ {
				session := createMockSession(uint32(id*100+j), server)
				stage.Lock()
				stage.clients[session] = session.charID
				stage.Unlock()

				stage.Lock()
				delete(stage.clients, session)
				stage.Unlock()
			}
		}(i)
	}

	// Concurrent reads
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 50; j++ {
				stage.RLock()
				_ = len(stage.clients)
				stage.RUnlock()
			}
		}()
	}

	wg.Wait()
}

func TestStageBroadcastMHF_EmptyStage(t *testing.T) {
	stage := NewStage("test")
	pkt := &mockPacket{opcode: 0x1234}

	// Should not panic with empty stage
	stage.BroadcastMHF(pkt, nil)
}
