package chat

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"
)

// EntityWithGetID helper type for testing
type EntityWithGetID struct {
	id int32
}

func (e *EntityWithGetID) GetID() int32 {
	return e.id
}

// Mock implementations for testing
type MockChannelDatabase struct {
	channels map[string]ChatChannelData
	mu       sync.RWMutex
	loadErr  error
	saveErr  error
	delErr   error
}

func NewMockChannelDatabase() *MockChannelDatabase {
	return &MockChannelDatabase{
		channels: make(map[string]ChatChannelData),
	}
}

func (m *MockChannelDatabase) LoadWorldChannels(ctx context.Context) ([]ChatChannelData, error) {
	if m.loadErr != nil {
		return nil, m.loadErr
	}

	m.mu.RLock()
	defer m.mu.RUnlock()

	var channels []ChatChannelData
	for _, channel := range m.channels {
		channels = append(channels, channel)
	}
	return channels, nil
}

func (m *MockChannelDatabase) SaveChannel(ctx context.Context, channel ChatChannelData) error {
	if m.saveErr != nil {
		return m.saveErr
	}

	m.mu.Lock()
	defer m.mu.Unlock()
	m.channels[channel.Name] = channel
	return nil
}

func (m *MockChannelDatabase) DeleteChannel(ctx context.Context, channelName string) error {
	if m.delErr != nil {
		return m.delErr
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.channels[channelName]; !exists {
		return fmt.Errorf("channel not found")
	}
	delete(m.channels, channelName)
	return nil
}

func (m *MockChannelDatabase) SetLoadError(err error) {
	m.loadErr = err
}

func (m *MockChannelDatabase) SetSaveError(err error) {
	m.saveErr = err
}

func (m *MockChannelDatabase) SetDeleteError(err error) {
	m.delErr = err
}

type MockClientManager struct {
	sentMessages     []ChannelMessage
	sentLists        map[int32][]ChannelInfo
	sentUpdates      []ChatUpdate
	sentUserLists    map[int32][]ChannelMember
	connectedClients map[int32]bool
	mu               sync.RWMutex
}

type ChatUpdate struct {
	CharacterID   int32
	ChannelName   string
	Action        int
	CharacterName string
}

func NewMockClientManager() *MockClientManager {
	return &MockClientManager{
		sentLists:        make(map[int32][]ChannelInfo),
		sentUserLists:    make(map[int32][]ChannelMember),
		connectedClients: make(map[int32]bool),
	}
}

func (m *MockClientManager) SendChannelList(characterID int32, channels []ChannelInfo) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.sentLists[characterID] = channels
	return nil
}

func (m *MockClientManager) SendChannelMessage(characterID int32, message ChannelMessage) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.sentMessages = append(m.sentMessages, message)
	return nil
}

func (m *MockClientManager) SendChannelUpdate(characterID int32, channelName string, action int, characterName string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.sentUpdates = append(m.sentUpdates, ChatUpdate{
		CharacterID:   characterID,
		ChannelName:   channelName,
		Action:        action,
		CharacterName: characterName,
	})
	return nil
}

func (m *MockClientManager) SendChannelUserList(characterID int32, channelName string, members []ChannelMember) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.sentUserLists[characterID] = members
	return nil
}

func (m *MockClientManager) IsClientConnected(characterID int32) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.connectedClients[characterID]
}

func (m *MockClientManager) SetClientConnected(characterID int32, connected bool) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.connectedClients[characterID] = connected
}

func (m *MockClientManager) GetSentMessages() []ChannelMessage {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return append([]ChannelMessage{}, m.sentMessages...)
}

type MockPlayerManager struct {
	players map[int32]PlayerInfo
	mu      sync.RWMutex
}

func NewMockPlayerManager() *MockPlayerManager {
	return &MockPlayerManager{
		players: make(map[int32]PlayerInfo),
	}
}

func (m *MockPlayerManager) GetPlayerInfo(characterID int32) (PlayerInfo, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	if player, exists := m.players[characterID]; exists {
		return player, nil
	}
	return PlayerInfo{}, fmt.Errorf("player not found")
}

func (m *MockPlayerManager) ValidatePlayer(characterID int32, levelReq, raceReq, classReq int32) bool {
	player, err := m.GetPlayerInfo(characterID)
	if err != nil {
		return false
	}

	if levelReq > 0 && player.Level < levelReq {
		return false
	}
	if raceReq > 0 && (raceReq&(1<<player.Race)) == 0 {
		return false
	}
	if classReq > 0 && (classReq&(1<<player.Class)) == 0 {
		return false
	}

	return true
}

func (m *MockPlayerManager) GetPlayerLanguages(characterID int32) ([]int32, error) {
	return []int32{0, 1}, nil // Default languages
}

func (m *MockPlayerManager) AddPlayer(player PlayerInfo) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.players[player.CharacterID] = player
}

type MockLanguageProcessor struct{}

func (m *MockLanguageProcessor) ProcessMessage(senderID, receiverID int32, message string, languageID int32) (string, error) {
	return message, nil // No scrambling for tests
}

func (m *MockLanguageProcessor) CanUnderstand(senderID, receiverID int32, languageID int32) bool {
	return true // Everyone understands everything in tests
}

func (m *MockLanguageProcessor) GetDefaultLanguage(characterID int32) int32 {
	return 0 // Common tongue
}

// Channel tests
func TestNewChannel(t *testing.T) {
	channel := NewChannel("test")

	if channel == nil {
		t.Fatal("NewChannel returned nil")
	}

	if channel.GetName() != "test" {
		t.Errorf("Channel name = %v, want test", channel.GetName())
	}

	if channel.GetNumClients() != 0 {
		t.Errorf("New channel should have 0 clients, got %v", channel.GetNumClients())
	}
}

func TestChannelSettersAndGetters(t *testing.T) {
	channel := NewChannel("test")

	// Test name
	channel.SetName("newname")
	if channel.GetName() != "newname" {
		t.Errorf("SetName failed: got %v, want newname", channel.GetName())
	}

	// Test password
	channel.SetPassword("secret")
	if !channel.HasPassword() {
		t.Error("HasPassword should return true after SetPassword")
	}

	if !channel.PasswordMatches("secret") {
		t.Error("PasswordMatches should return true for correct password")
	}

	if channel.PasswordMatches("wrong") {
		t.Error("PasswordMatches should return false for incorrect password")
	}

	// Test type
	channel.SetType(ChannelTypeWorld)
	if channel.GetType() != ChannelTypeWorld {
		t.Errorf("SetType failed: got %v, want %v", channel.GetType(), ChannelTypeWorld)
	}

	// Test restrictions
	channel.SetLevelRestriction(50)
	if !channel.CanJoinChannelByLevel(50) {
		t.Error("CanJoinChannelByLevel should allow level 50")
	}

	if channel.CanJoinChannelByLevel(49) {
		t.Error("CanJoinChannelByLevel should not allow level 49")
	}

	// Test race restriction (bitmask)
	channel.SetRacesAllowed(1 << 1) // Only race ID 1 allowed
	if !channel.CanJoinChannelByRace(1) {
		t.Error("CanJoinChannelByRace should allow race 1")
	}

	if channel.CanJoinChannelByRace(2) {
		t.Error("CanJoinChannelByRace should not allow race 2")
	}

	// Test class restriction (bitmask)
	channel.SetClassesAllowed(1 << 3) // Only class ID 3 allowed
	if !channel.CanJoinChannelByClass(3) {
		t.Error("CanJoinChannelByClass should allow class 3")
	}

	if channel.CanJoinChannelByClass(4) {
		t.Error("CanJoinChannelByClass should not allow class 4")
	}
}

func TestChannelMembership(t *testing.T) {
	channel := NewChannel("test")

	// Test initial state
	if channel.IsInChannel(100) {
		t.Error("IsInChannel should return false for new channel")
	}

	if !channel.IsEmpty() {
		t.Error("New channel should be empty")
	}

	// Test joining
	err := channel.JoinChannel(100)
	if err != nil {
		t.Errorf("JoinChannel failed: %v", err)
	}

	if !channel.IsInChannel(100) {
		t.Error("IsInChannel should return true after joining")
	}

	if channel.GetNumClients() != 1 {
		t.Errorf("GetNumClients = %v, want 1", channel.GetNumClients())
	}

	if channel.IsEmpty() {
		t.Error("Channel should not be empty after joining")
	}

	// Test duplicate join
	err = channel.JoinChannel(100)
	if err == nil {
		t.Error("JoinChannel should fail for duplicate member")
	}

	// Test multiple members
	err = channel.JoinChannel(200)
	if err != nil {
		t.Errorf("JoinChannel failed for second member: %v", err)
	}

	if channel.GetNumClients() != 2 {
		t.Errorf("GetNumClients = %v, want 2", channel.GetNumClients())
	}

	// Test GetMembers
	members := channel.GetMembers()
	if len(members) != 2 {
		t.Errorf("GetMembers returned %v members, want 2", len(members))
	}

	// Verify members contains both IDs
	found100, found200 := false, false
	for _, id := range members {
		if id == 100 {
			found100 = true
		}
		if id == 200 {
			found200 = true
		}
	}

	if !found100 || !found200 {
		t.Error("GetMembers should contain both member IDs")
	}

	// Test leaving
	err = channel.LeaveChannel(100)
	if err != nil {
		t.Errorf("LeaveChannel failed: %v", err)
	}

	if channel.IsInChannel(100) {
		t.Error("IsInChannel should return false after leaving")
	}

	if channel.GetNumClients() != 1 {
		t.Errorf("GetNumClients = %v, want 1 after leaving", channel.GetNumClients())
	}

	// Test leaving non-member
	err = channel.LeaveChannel(300)
	if err == nil {
		t.Error("LeaveChannel should fail for non-member")
	}

	// Test leaving last member
	err = channel.LeaveChannel(200)
	if err != nil {
		t.Errorf("LeaveChannel failed for last member: %v", err)
	}

	if !channel.IsEmpty() {
		t.Error("Channel should be empty after all members leave")
	}
}

func TestChannelValidateJoin(t *testing.T) {
	channel := NewChannel("test")
	channel.SetPassword("secret")
	channel.SetLevelRestriction(10)
	channel.SetRacesAllowed(1 << 1)   // Race 1 only
	channel.SetClassesAllowed(1 << 2) // Class 2 only

	tests := []struct {
		name     string
		level    int32
		race     int32
		class    int32
		password string
		wantErr  bool
	}{
		{
			name:     "valid join",
			level:    15,
			race:     1,
			class:    2,
			password: "secret",
			wantErr:  false,
		},
		{
			name:     "wrong password",
			level:    15,
			race:     1,
			class:    2,
			password: "wrong",
			wantErr:  true,
		},
		{
			name:     "level too low",
			level:    5,
			race:     1,
			class:    2,
			password: "secret",
			wantErr:  true,
		},
		{
			name:     "wrong race",
			level:    15,
			race:     2,
			class:    2,
			password: "secret",
			wantErr:  true,
		},
		{
			name:     "wrong class",
			level:    15,
			race:     1,
			class:    3,
			password: "secret",
			wantErr:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := channel.ValidateJoin(tt.level, tt.race, tt.class, tt.password)
			if (err != nil) != tt.wantErr {
				t.Errorf("ValidateJoin() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestChannelGetChannelInfo(t *testing.T) {
	channel := NewChannel("testchannel")
	channel.SetType(ChannelTypeWorld)
	channel.SetPassword("secret")
	channel.SetLevelRestriction(20)
	channel.SetRacesAllowed(15)   // Multiple races
	channel.SetClassesAllowed(31) // Multiple classes
	channel.JoinChannel(100)
	channel.JoinChannel(200)

	info := channel.GetChannelInfo()

	if info.Name != "testchannel" {
		t.Errorf("ChannelInfo Name = %v, want testchannel", info.Name)
	}

	if !info.HasPassword {
		t.Error("ChannelInfo should indicate has password")
	}

	if info.MemberCount != 2 {
		t.Errorf("ChannelInfo MemberCount = %v, want 2", info.MemberCount)
	}

	if info.LevelRestriction != 20 {
		t.Errorf("ChannelInfo LevelRestriction = %v, want 20", info.LevelRestriction)
	}

	if info.ChannelType != ChannelTypeWorld {
		t.Errorf("ChannelInfo ChannelType = %v, want %v", info.ChannelType, ChannelTypeWorld)
	}
}

func TestChannelCopy(t *testing.T) {
	original := NewChannel("original")
	original.SetPassword("secret")
	original.SetType(ChannelTypeCustom)
	original.SetLevelRestriction(25)
	original.JoinChannel(100)
	original.JoinChannel(200)

	copy := original.Copy()

	if copy == original {
		t.Error("Copy should return different instance")
	}

	if copy.GetName() != original.GetName() {
		t.Error("Copy should have same name")
	}

	if copy.GetType() != original.GetType() {
		t.Error("Copy should have same type")
	}

	if copy.GetNumClients() != original.GetNumClients() {
		t.Error("Copy should have same member count")
	}

	// Test that modifying copy doesn't affect original
	copy.JoinChannel(300)
	if original.GetNumClients() == copy.GetNumClients() {
		t.Error("Modifying copy should not affect original")
	}
}

// Database tests
func TestMockChannelDatabase(t *testing.T) {
	db := NewMockChannelDatabase()
	ctx := context.Background()

	// Test empty database
	channels, err := db.LoadWorldChannels(ctx)
	if err != nil {
		t.Errorf("LoadWorldChannels failed: %v", err)
	}

	if len(channels) != 0 {
		t.Errorf("Expected 0 channels, got %v", len(channels))
	}

	// Test saving channel
	testChannel := ChatChannelData{
		Name:             "testchannel",
		Password:         "secret",
		LevelRestriction: 10,
		ClassRestriction: 15,
		RaceRestriction:  7,
	}

	err = db.SaveChannel(ctx, testChannel)
	if err != nil {
		t.Errorf("SaveChannel failed: %v", err)
	}

	// Test loading after save
	channels, err = db.LoadWorldChannels(ctx)
	if err != nil {
		t.Errorf("LoadWorldChannels failed: %v", err)
	}

	if len(channels) != 1 {
		t.Errorf("Expected 1 channel, got %v", len(channels))
	}

	if channels[0].Name != testChannel.Name {
		t.Errorf("Channel name = %v, want %v", channels[0].Name, testChannel.Name)
	}

	// Test delete
	err = db.DeleteChannel(ctx, "testchannel")
	if err != nil {
		t.Errorf("DeleteChannel failed: %v", err)
	}

	// Test delete non-existent
	err = db.DeleteChannel(ctx, "nonexistent")
	if err == nil {
		t.Error("DeleteChannel should fail for non-existent channel")
	}

	// Test error conditions
	db.SetLoadError(fmt.Errorf("load error"))
	_, err = db.LoadWorldChannels(ctx)
	if err == nil {
		t.Error("LoadWorldChannels should return error when set")
	}

	db.SetSaveError(fmt.Errorf("save error"))
	err = db.SaveChannel(ctx, testChannel)
	if err == nil {
		t.Error("SaveChannel should return error when set")
	}

	db.SetDeleteError(fmt.Errorf("delete error"))
	err = db.DeleteChannel(ctx, "test")
	if err == nil {
		t.Error("DeleteChannel should return error when set")
	}
}

// Interface tests
func TestEntityChatAdapter(t *testing.T) {
	playerManager := NewMockPlayerManager()
	playerManager.AddPlayer(PlayerInfo{
		CharacterID:   100,
		CharacterName: "TestPlayer",
		Level:         25,
		Race:          1,
		Class:         2,
		IsOnline:      true,
	})

	entityWithGetID := &EntityWithGetID{id: 100}

	adapter := &EntityChatAdapter{
		entity:        entityWithGetID,
		playerManager: playerManager,
	}

	if adapter.GetCharacterID() != 100 {
		t.Errorf("GetCharacterID() = %v, want 100", adapter.GetCharacterID())
	}

	if adapter.GetCharacterName() != "TestPlayer" {
		t.Errorf("GetCharacterName() = %v, want TestPlayer", adapter.GetCharacterName())
	}

	if adapter.GetLevel() != 25 {
		t.Errorf("GetLevel() = %v, want 25", adapter.GetLevel())
	}

	if adapter.GetRace() != 1 {
		t.Errorf("GetRace() = %v, want 1", adapter.GetRace())
	}

	if adapter.GetClass() != 2 {
		t.Errorf("GetClass() = %v, want 2", adapter.GetClass())
	}

	// Test with non-existent player
	entityWithGetID2 := &EntityWithGetID{id: 999}

	adapter2 := &EntityChatAdapter{
		entity:        entityWithGetID2,
		playerManager: playerManager,
	}

	if adapter2.GetCharacterName() != "" {
		t.Error("GetCharacterName should return empty string for non-existent player")
	}

	if adapter2.GetLevel() != 0 {
		t.Error("GetLevel should return 0 for non-existent player")
	}
}

// Constants tests
func TestConstants(t *testing.T) {
	// Test channel types
	if ChannelTypeNone != 0 {
		t.Errorf("ChannelTypeNone = %v, want 0", ChannelTypeNone)
	}

	if ChannelTypeWorld != 1 {
		t.Errorf("ChannelTypeWorld = %v, want 1", ChannelTypeWorld)
	}

	if ChannelTypeCustom != 2 {
		t.Errorf("ChannelTypeCustom = %v, want 2", ChannelTypeCustom)
	}

	// Test chat actions
	if ChatChannelJoin != 0 {
		t.Errorf("ChatChannelJoin = %v, want 0", ChatChannelJoin)
	}

	if ChatChannelLeave != 1 {
		t.Errorf("ChatChannelLeave = %v, want 1", ChatChannelLeave)
	}

	// Test restrictions
	if NoLevelRestriction != 0 {
		t.Errorf("NoLevelRestriction = %v, want 0", NoLevelRestriction)
	}

	if NoRaceRestriction != 0 {
		t.Errorf("NoRaceRestriction = %v, want 0", NoRaceRestriction)
	}

	if NoClassRestriction != 0 {
		t.Errorf("NoClassRestriction = %v, want 0", NoClassRestriction)
	}
}

// Concurrency tests
func TestChannelConcurrency(t *testing.T) {
	channel := NewChannel("concurrent")
	var wg sync.WaitGroup

	// Concurrent joins
	for i := range 100 {
		wg.Add(1)
		go func(id int32) {
			defer wg.Done()
			channel.JoinChannel(id)
		}(int32(i))
	}

	// Concurrent reads
	for i := range 50 {
		wg.Add(1)
		go func(id int32) {
			defer wg.Done()
			channel.IsInChannel(id)
			channel.GetMembers()
			channel.GetChannelInfo()
		}(int32(i))
	}

	wg.Wait()

	// Verify final state
	if channel.GetNumClients() != 100 {
		t.Errorf("After concurrent joins, got %v members, want 100", channel.GetNumClients())
	}

	// Concurrent leaves
	for i := range 50 {
		wg.Add(1)
		go func(id int32) {
			defer wg.Done()
			channel.LeaveChannel(id)
		}(int32(i))
	}

	wg.Wait()

	if channel.GetNumClients() != 50 {
		t.Errorf("After concurrent leaves, got %v members, want 50", channel.GetNumClients())
	}
}

func TestMockClientManager(t *testing.T) {
	client := NewMockClientManager()

	// Test client connection status
	if client.IsClientConnected(100) {
		t.Error("IsClientConnected should return false initially")
	}

	client.SetClientConnected(100, true)
	if !client.IsClientConnected(100) {
		t.Error("IsClientConnected should return true after setting")
	}

	// Test channel list
	channels := []ChannelInfo{
		{Name: "test1", MemberCount: 5},
		{Name: "test2", MemberCount: 10},
	}

	err := client.SendChannelList(100, channels)
	if err != nil {
		t.Errorf("SendChannelList failed: %v", err)
	}

	// Test channel message
	message := ChannelMessage{
		SenderID:    200,
		SenderName:  "TestSender",
		Message:     "Hello world",
		ChannelName: "test",
		Timestamp:   time.Now(),
	}

	err = client.SendChannelMessage(100, message)
	if err != nil {
		t.Errorf("SendChannelMessage failed: %v", err)
	}

	sentMessages := client.GetSentMessages()
	if len(sentMessages) != 1 {
		t.Errorf("Expected 1 sent message, got %v", len(sentMessages))
	}

	if sentMessages[0].Message != "Hello world" {
		t.Errorf("Sent message = %v, want Hello world", sentMessages[0].Message)
	}
}

func TestMockPlayerManager(t *testing.T) {
	playerMgr := NewMockPlayerManager()

	// Test non-existent player
	_, err := playerMgr.GetPlayerInfo(999)
	if err == nil {
		t.Error("GetPlayerInfo should fail for non-existent player")
	}

	// Add player
	player := PlayerInfo{
		CharacterID:   100,
		CharacterName: "TestPlayer",
		Level:         30,
		Race:          2,
		Class:         4,
		IsOnline:      true,
	}

	playerMgr.AddPlayer(player)

	// Test existing player
	retrieved, err := playerMgr.GetPlayerInfo(100)
	if err != nil {
		t.Errorf("GetPlayerInfo failed: %v", err)
	}

	if retrieved.CharacterName != "TestPlayer" {
		t.Errorf("Player name = %v, want TestPlayer", retrieved.CharacterName)
	}

	// Test validation
	if !playerMgr.ValidatePlayer(100, 25, 0, 0) {
		t.Error("ValidatePlayer should pass for level requirement")
	}

	if playerMgr.ValidatePlayer(100, 35, 0, 0) {
		t.Error("ValidatePlayer should fail for level requirement")
	}

	// Test languages
	languages, err := playerMgr.GetPlayerLanguages(100)
	if err != nil {
		t.Errorf("GetPlayerLanguages failed: %v", err)
	}

	if len(languages) == 0 {
		t.Error("GetPlayerLanguages should return some languages")
	}
}

func TestMockLanguageProcessor(t *testing.T) {
	processor := &MockLanguageProcessor{}

	// Test message processing
	processed, err := processor.ProcessMessage(100, 200, "test message", 0)
	if err != nil {
		t.Errorf("ProcessMessage failed: %v", err)
	}

	if processed != "test message" {
		t.Errorf("ProcessMessage = %v, want test message", processed)
	}

	// Test understanding
	if !processor.CanUnderstand(100, 200, 0) {
		t.Error("CanUnderstand should return true in mock")
	}

	// Test default language
	if processor.GetDefaultLanguage(100) != 0 {
		t.Error("GetDefaultLanguage should return 0 in mock")
	}
}

// Benchmarks
func BenchmarkChannelJoin(b *testing.B) {
	channel := NewChannel("benchmark")

	for i := 0; b.Loop(); i++ {
		channel.JoinChannel(int32(i))
	}
}

func BenchmarkChannelIsInChannel(b *testing.B) {
	channel := NewChannel("benchmark")

	for i := range 1000 {
		channel.JoinChannel(int32(i))
	}

	for i := 0; b.Loop(); i++ {
		channel.IsInChannel(int32(i % 1000))
	}
}

func BenchmarkChannelGetMembers(b *testing.B) {
	channel := NewChannel("benchmark")

	for i := range 1000 {
		channel.JoinChannel(int32(i))
	}

	for b.Loop() {
		channel.GetMembers()
	}
}
