package channelserver

import (
	"net"
	"slices"
	"strings"
	"testing"

	"erupe-ce/common/byteframe"
	"erupe-ce/common/mhfcourse"
	cfg "erupe-ce/config"
	"erupe-ce/network/binpacket"
	"erupe-ce/network/mhfpacket"
)

// TestSendServerChatMessage verifies that server chat messages are correctly formatted and queued
func TestSendServerChatMessage(t *testing.T) {
	tests := []struct {
		name    string
		message string
		wantErr bool
	}{
		{
			name:    "simple_message",
			message: "Hello, World!",
			wantErr: false,
		},
		{
			name:    "empty_message",
			message: "",
			wantErr: false,
		},
		{
			name:    "special_characters",
			message: "Test @#$%^&*()",
			wantErr: false,
		},
		{
			name:    "unicode_message",
			message: "テスト メッセージ",
			wantErr: false,
		},
		{
			name:    "long_message",
			message: strings.Repeat("A", 1000),
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
			s := createTestSession(mock)

			// Send the chat message
			sendServerChatMessage(s, tt.message)

			// Verify the message was queued
			if len(s.sendPackets) == 0 {
				t.Error("no packets were queued")
				return
			}

			// Read from the channel with timeout to avoid hanging
			select {
			case pkt := <-s.sendPackets:
				if pkt.data == nil {
					t.Error("packet data is nil")
				}
				// Verify it's an MHFPacket (contains opcode)
				if len(pkt.data) < 2 {
					t.Error("packet too short to contain opcode")
				}
			default:
				t.Error("no packet available in channel")
			}
		})
	}
}

// TestHandleMsgSysCastBinary_SimpleData verifies basic data message handling
func TestHandleMsgSysCastBinary_SimpleData(t *testing.T) {
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)
	s.charID = 54321
	s.stage = NewStage("test_stage")
	s.stage.clients[s] = s.charID
	s.server.sessions = make(map[net.Conn]*Session)

	// Create a data message payload
	bf := byteframe.NewByteFrame()
	bf.SetLE()
	bf.WriteUint32(0xDEADBEEF)

	pkt := &mhfpacket.MsgSysCastBinary{
		Unk:            0,
		BroadcastType:  BroadcastTypeStage,
		MessageType:    BinaryMessageTypeData,
		RawDataPayload: bf.Data(),
	}

	// Should not panic
	handleMsgSysCastBinary(s, pkt)
}

// TestHandleMsgSysCastBinary_DiceCommand verifies the @dice command
func TestHandleMsgSysCastBinary_DiceCommand(t *testing.T) {
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)
	s.charID = 99999
	s.stage = NewStage("test_stage")
	s.stage.clients[s] = s.charID
	s.server.sessions = make(map[net.Conn]*Session)

	// Build a chat message with @dice command
	bf := byteframe.NewByteFrame()
	bf.SetLE()
	msg := &binpacket.MsgBinChat{
		Unk0:       0,
		Type:       5,
		Flags:      0x80,
		Message:    "@dice",
		SenderName: "TestPlayer",
	}
	_ = msg.Build(bf)

	pkt := &mhfpacket.MsgSysCastBinary{
		Unk:            0,
		BroadcastType:  BroadcastTypeStage,
		MessageType:    BinaryMessageTypeChat,
		RawDataPayload: bf.Data(),
	}

	// Should execute dice command and return
	handleMsgSysCastBinary(s, pkt)

	// Verify a response was queued (dice result)
	if len(s.sendPackets) == 0 {
		t.Error("dice command did not queue a response")
	}
}

// TestBroadcastTypes verifies different broadcast types are handled
func TestBroadcastTypes(t *testing.T) {
	tests := []struct {
		name          string
		broadcastType uint8
		buildPayload  func() []byte
	}{
		{
			name:          "broadcast_targeted",
			broadcastType: BroadcastTypeTargeted,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetBE() // Targeted uses BE
				msg := &binpacket.MsgBinTargeted{
					TargetCharIDs:  []uint32{1, 2, 3},
					RawDataPayload: []byte{0xDE, 0xAD, 0xBE, 0xEF},
				}
				_ = msg.Build(bf)
				return bf.Data()
			},
		},
		{
			name:          "broadcast_stage",
			broadcastType: BroadcastTypeStage,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0x12345678)
				return bf.Data()
			},
		},
		{
			name:          "broadcast_server",
			broadcastType: BroadcastTypeServer,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0x12345678)
				return bf.Data()
			},
		},
		{
			name:          "broadcast_world",
			broadcastType: BroadcastTypeWorld,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0x12345678)
				return bf.Data()
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
			s := createTestSession(mock)
			s.charID = 22222
			s.stage = NewStage("test_stage")
			s.stage.clients[s] = s.charID
			s.server.sessions = make(map[net.Conn]*Session)

			pkt := &mhfpacket.MsgSysCastBinary{
				Unk:            0,
				BroadcastType:  tt.broadcastType,
				MessageType:    BinaryMessageTypeState,
				RawDataPayload: tt.buildPayload(),
			}

			// Should handle without panic
			handleMsgSysCastBinary(s, pkt)
		})
	}
}

// TestBinaryMessageTypes verifies different message types are handled
func TestBinaryMessageTypes(t *testing.T) {
	tests := []struct {
		name         string
		messageType  uint8
		buildPayload func() []byte
	}{
		{
			name:        "msg_type_state",
			messageType: BinaryMessageTypeState,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0xDEADBEEF)
				return bf.Data()
			},
		},
		{
			name:        "msg_type_chat",
			messageType: BinaryMessageTypeChat,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				msg := &binpacket.MsgBinChat{
					Unk0:       0,
					Type:       5,
					Flags:      0x80,
					Message:    "test",
					SenderName: "Player",
				}
				_ = msg.Build(bf)
				return bf.Data()
			},
		},
		{
			name:        "msg_type_quest",
			messageType: BinaryMessageTypeQuest,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0xDEADBEEF)
				return bf.Data()
			},
		},
		{
			name:        "msg_type_data",
			messageType: BinaryMessageTypeData,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0xDEADBEEF)
				return bf.Data()
			},
		},
		{
			name:        "msg_type_mail_notify",
			messageType: BinaryMessageTypeMailNotify,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0xDEADBEEF)
				return bf.Data()
			},
		},
		{
			name:        "msg_type_emote",
			messageType: BinaryMessageTypeEmote,
			buildPayload: func() []byte {
				bf := byteframe.NewByteFrame()
				bf.SetLE()
				bf.WriteUint32(0xDEADBEEF)
				return bf.Data()
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
			s := createTestSession(mock)
			s.charID = 33333
			s.stage = NewStage("test_stage")
			s.stage.clients[s] = s.charID
			s.server.sessions = make(map[net.Conn]*Session)

			pkt := &mhfpacket.MsgSysCastBinary{
				Unk:            0,
				BroadcastType:  BroadcastTypeStage,
				MessageType:    tt.messageType,
				RawDataPayload: tt.buildPayload(),
			}

			// Should handle without panic
			handleMsgSysCastBinary(s, pkt)
		})
	}
}

// TestSlicesContainsUsage verifies the slices.Contains function works correctly
func TestSlicesContainsUsage(t *testing.T) {
	tests := []struct {
		name     string
		items    []cfg.Course
		target   cfg.Course
		expected bool
	}{
		{
			name: "item_exists",
			items: []cfg.Course{
				{Name: "Course1", Enabled: true},
				{Name: "Course2", Enabled: false},
			},
			target:   cfg.Course{Name: "Course1", Enabled: true},
			expected: true,
		},
		{
			name: "item_not_found",
			items: []cfg.Course{
				{Name: "Course1", Enabled: true},
				{Name: "Course2", Enabled: false},
			},
			target:   cfg.Course{Name: "Course3", Enabled: true},
			expected: false,
		},
		{
			name:     "empty_slice",
			items:    []cfg.Course{},
			target:   cfg.Course{Name: "Course1", Enabled: true},
			expected: false,
		},
		{
			name: "enabled_mismatch",
			items: []cfg.Course{
				{Name: "Course1", Enabled: true},
			},
			target:   cfg.Course{Name: "Course1", Enabled: false},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := slices.Contains(tt.items, tt.target)
			if result != tt.expected {
				t.Errorf("slices.Contains() = %v, want %v", result, tt.expected)
			}
		})
	}
}

// TestSlicesIndexFuncUsage verifies the slices.IndexFunc function works correctly
func TestSlicesIndexFuncUsage(t *testing.T) {
	tests := []struct {
		name      string
		courses   []mhfcourse.Course
		predicate func(mhfcourse.Course) bool
		expected  int
	}{
		{
			name:    "empty_slice",
			courses: []mhfcourse.Course{},
			predicate: func(c mhfcourse.Course) bool {
				return true
			},
			expected: -1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := slices.IndexFunc(tt.courses, tt.predicate)
			if result != tt.expected {
				t.Errorf("slices.IndexFunc() = %d, want %d", result, tt.expected)
			}
		})
	}
}

// TestChatMessageParsing verifies chat message extraction from binary payload
func TestChatMessageParsing(t *testing.T) {
	tests := []struct {
		name           string
		messageContent string
		authorName     string
	}{
		{
			name:           "standard_message",
			messageContent: "Hello World",
			authorName:     "Player123",
		},
		{
			name:           "special_chars_message",
			messageContent: "Test@#$%^&*()",
			authorName:     "SpecialUser",
		},
		{
			name:           "empty_message",
			messageContent: "",
			authorName:     "Silent",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Build a binary chat message
			bf := byteframe.NewByteFrame()
			bf.SetLE()
			msg := &binpacket.MsgBinChat{
				Unk0:       0,
				Type:       5,
				Flags:      0x80,
				Message:    tt.messageContent,
				SenderName: tt.authorName,
			}
			_ = msg.Build(bf)

			// Parse it back
			parseBf := byteframe.NewByteFrameFromBytes(bf.Data())
			parseBf.SetLE()
			_, _ = parseBf.Seek(8, 0) // Skip initial bytes

			message := string(parseBf.ReadNullTerminatedBytes())
			author := string(parseBf.ReadNullTerminatedBytes())

			if message != tt.messageContent {
				t.Errorf("message mismatch: got %q, want %q", message, tt.messageContent)
			}
			if author != tt.authorName {
				t.Errorf("author mismatch: got %q, want %q", author, tt.authorName)
			}
		})
	}
}

// TestBinaryMessageTypeEnums verifies message type constants
func TestBinaryMessageTypeEnums(t *testing.T) {
	tests := []struct {
		name    string
		typeVal uint8
		typeID  uint8
	}{
		{
			name:    "state_type",
			typeVal: BinaryMessageTypeState,
			typeID:  0,
		},
		{
			name:    "chat_type",
			typeVal: BinaryMessageTypeChat,
			typeID:  1,
		},
		{
			name:    "quest_type",
			typeVal: BinaryMessageTypeQuest,
			typeID:  2,
		},
		{
			name:    "data_type",
			typeVal: BinaryMessageTypeData,
			typeID:  3,
		},
		{
			name:    "mail_notify_type",
			typeVal: BinaryMessageTypeMailNotify,
			typeID:  4,
		},
		{
			name:    "emote_type",
			typeVal: BinaryMessageTypeEmote,
			typeID:  6,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.typeVal != tt.typeID {
				t.Errorf("type mismatch: got %d, want %d", tt.typeVal, tt.typeID)
			}
		})
	}
}

// TestBroadcastTypeEnums verifies broadcast type constants
func TestBroadcastTypeEnums(t *testing.T) {
	tests := []struct {
		name    string
		typeVal uint8
		typeID  uint8
	}{
		{
			name:    "targeted_type",
			typeVal: BroadcastTypeTargeted,
			typeID:  0x01,
		},
		{
			name:    "stage_type",
			typeVal: BroadcastTypeStage,
			typeID:  0x03,
		},
		{
			name:    "server_type",
			typeVal: BroadcastTypeServer,
			typeID:  0x06,
		},
		{
			name:    "world_type",
			typeVal: BroadcastTypeWorld,
			typeID:  0x0a,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.typeVal != tt.typeID {
				t.Errorf("type mismatch: got %d, want %d", tt.typeVal, tt.typeID)
			}
		})
	}
}

// TestPayloadHandling verifies raw payload handling in different scenarios
func TestPayloadHandling(t *testing.T) {
	tests := []struct {
		name          string
		payloadSize   int
		broadcastType uint8
		messageType   uint8
	}{
		{
			name:          "empty_payload",
			payloadSize:   0,
			broadcastType: BroadcastTypeStage,
			messageType:   BinaryMessageTypeData,
		},
		{
			name:          "small_payload",
			payloadSize:   4,
			broadcastType: BroadcastTypeStage,
			messageType:   BinaryMessageTypeData,
		},
		{
			name:          "large_payload",
			payloadSize:   10000,
			broadcastType: BroadcastTypeStage,
			messageType:   BinaryMessageTypeData,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
			s := createTestSession(mock)
			s.charID = 44444
			s.stage = NewStage("test_stage")
			s.stage.clients[s] = s.charID
			s.server.sessions = make(map[net.Conn]*Session)

			// Create payload of specified size
			payload := make([]byte, tt.payloadSize)
			for i := 0; i < len(payload); i++ {
				payload[i] = byte(i % 256)
			}

			pkt := &mhfpacket.MsgSysCastBinary{
				Unk:            0,
				BroadcastType:  tt.broadcastType,
				MessageType:    tt.messageType,
				RawDataPayload: payload,
			}

			// Should handle without panic
			handleMsgSysCastBinary(s, pkt)
		})
	}
}

// TestCastedBinaryPacketConstruction verifies correct packet construction
func TestCastedBinaryPacketConstruction(t *testing.T) {
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)
	s.charID = 77777

	message := "Test message"

	sendServerChatMessage(s, message)

	// Verify a packet was queued
	if len(s.sendPackets) == 0 {
		t.Fatal("no packets queued")
	}

	// Extract packet from channel
	pkt := <-s.sendPackets

	if pkt.data == nil {
		t.Error("packet data is nil")
	}

	// The packet should be at least a valid MHF packet with opcode
	if len(pkt.data) < 2 {
		t.Error("packet too short")
	}
}

// TestNilPayloadHandling verifies safe handling of nil payloads
func TestNilPayloadHandling(t *testing.T) {
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)
	s.charID = 55555
	s.stage = NewStage("test_stage")
	s.stage.clients[s] = s.charID
	s.server.sessions = make(map[net.Conn]*Session)

	pkt := &mhfpacket.MsgSysCastBinary{
		Unk:            0,
		BroadcastType:  BroadcastTypeStage,
		MessageType:    BinaryMessageTypeData,
		RawDataPayload: nil,
	}

	// Should handle nil payload without panic
	handleMsgSysCastBinary(s, pkt)
}

// BenchmarkSendServerChatMessage benchmarks the chat message sending
func BenchmarkSendServerChatMessage(b *testing.B) {
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)

	message := "This is a benchmark message"

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sendServerChatMessage(s, message)
	}
}

// BenchmarkHandleMsgSysCastBinary benchmarks the packet handling
func BenchmarkHandleMsgSysCastBinary(b *testing.B) {
	mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
	s := createTestSession(mock)
	s.charID = 99999
	s.stage = NewStage("test_stage")
	s.stage.clients[s] = s.charID
	s.server.sessions = make(map[net.Conn]*Session)

	// Prepare packet
	bf := byteframe.NewByteFrame()
	bf.SetLE()
	bf.WriteUint32(0x12345678)

	pkt := &mhfpacket.MsgSysCastBinary{
		Unk:            0,
		BroadcastType:  BroadcastTypeStage,
		MessageType:    BinaryMessageTypeData,
		RawDataPayload: bf.Data(),
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		handleMsgSysCastBinary(s, pkt)
	}
}

// BenchmarkSlicesContains benchmarks the slices.Contains function
func BenchmarkSlicesContains(b *testing.B) {
	courses := []cfg.Course{
		{Name: "Course1", Enabled: true},
		{Name: "Course2", Enabled: false},
		{Name: "Course3", Enabled: true},
		{Name: "Course4", Enabled: false},
		{Name: "Course5", Enabled: true},
	}

	target := cfg.Course{Name: "Course3", Enabled: true}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = slices.Contains(courses, target)
	}
}

// BenchmarkSlicesIndexFunc benchmarks the slices.IndexFunc function
func BenchmarkSlicesIndexFunc(b *testing.B) {
	// Create mock courses (empty as real data not needed for benchmark)
	courses := make([]mhfcourse.Course, 100)

	predicate := func(c mhfcourse.Course) bool {
		return false // Worst case - always iterate to end
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = slices.IndexFunc(courses, predicate)
	}
}

func TestEmptyCastedBinaryHandler(t *testing.T) {
	server := createMockServer()
	session := createMockSession(1, server)

	defer func() {
		if r := recover(); r != nil {
			t.Errorf("handleMsgSysCastedBinary panicked: %v", r)
		}
	}()
	handleMsgSysCastedBinary(session, nil)
}
