package chat

import (
	"context"
	"fmt"
	"strings"
	"time"
)

// NewChatManager creates a new chat manager instance
func NewChatManager(database ChannelDatabase, clientManager ClientManager, playerManager PlayerManager, languageProcessor LanguageProcessor) *ChatManager {
	return &ChatManager{
		channels:          make(map[string]*Channel),
		database:          database,
		clientManager:     clientManager,
		playerManager:     playerManager,
		languageProcessor: languageProcessor,
	}
}

// Initialize loads world channels from database and prepares the chat system
func (cm *ChatManager) Initialize(ctx context.Context) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	// Load world channels from database
	worldChannels, err := cm.database.LoadWorldChannels(ctx)
	if err != nil {
		return fmt.Errorf("failed to load world channels: %w", err)
	}

	// Create world channels
	for _, channelData := range worldChannels {
		channel := &Channel{
			name:             channelData.Name,
			password:         channelData.Password,
			channelType:      ChannelTypeWorld,
			levelRestriction: channelData.LevelRestriction,
			raceRestriction:  channelData.RaceRestriction,
			classRestriction: channelData.ClassRestriction,
			members:          make([]int32, 0),
			created:          time.Now(),
		}
		cm.channels[strings.ToLower(channelData.Name)] = channel
	}

	return nil
}

// AddChannel adds a new channel to the manager (used for world channels loaded from database)
func (cm *ChatManager) AddChannel(channel *Channel) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	cm.channels[strings.ToLower(channel.name)] = channel
}

// GetNumChannels returns the total number of channels
func (cm *ChatManager) GetNumChannels() int {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	return len(cm.channels)
}

// GetWorldChannelList returns filtered list of world channels for a client
func (cm *ChatManager) GetWorldChannelList(characterID int32) ([]ChannelInfo, error) {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	playerInfo, err := cm.playerManager.GetPlayerInfo(characterID)
	if err != nil {
		return nil, fmt.Errorf("failed to get player info: %w", err)
	}

	var channelList []ChannelInfo
	for _, channel := range cm.channels {
		if channel.channelType == ChannelTypeWorld {
			// Check if player can join based on restrictions
			if cm.canJoinChannel(playerInfo.Level, playerInfo.Race, playerInfo.Class,
				channel.levelRestriction, channel.raceRestriction, channel.classRestriction) {

				channelInfo := ChannelInfo{
					Name:             channel.name,
					HasPassword:      channel.password != "",
					MemberCount:      len(channel.members),
					LevelRestriction: channel.levelRestriction,
					RaceRestriction:  channel.raceRestriction,
					ClassRestriction: channel.classRestriction,
					ChannelType:      channel.channelType,
				}
				channelList = append(channelList, channelInfo)
			}
		}
	}

	return channelList, nil
}

// ChannelExists checks if a channel with the given name exists
func (cm *ChatManager) ChannelExists(channelName string) bool {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	_, exists := cm.channels[strings.ToLower(channelName)]
	return exists
}

// HasPassword checks if a channel has a password
func (cm *ChatManager) HasPassword(channelName string) bool {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	if channel, exists := cm.channels[strings.ToLower(channelName)]; exists {
		return channel.password != ""
	}
	return false
}

// PasswordMatches checks if the provided password matches the channel password
func (cm *ChatManager) PasswordMatches(channelName, password string) bool {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	if channel, exists := cm.channels[strings.ToLower(channelName)]; exists {
		return channel.password == password
	}
	return false
}

// CreateChannel creates a new custom channel
func (cm *ChatManager) CreateChannel(channelName string, password ...string) error {
	if len(channelName) > MaxChannelNameLength {
		return fmt.Errorf("channel name too long: %d > %d", len(channelName), MaxChannelNameLength)
	}

	cm.mu.Lock()
	defer cm.mu.Unlock()

	// Check if channel already exists
	if _, exists := cm.channels[strings.ToLower(channelName)]; exists {
		return fmt.Errorf("channel %s already exists", channelName)
	}

	// Create new custom channel
	channel := &Channel{
		name:        channelName,
		channelType: ChannelTypeCustom,
		members:     make([]int32, 0),
		created:     time.Now(),
	}

	// Set password if provided
	if len(password) > 0 && password[0] != "" {
		if len(password[0]) > MaxChannelPasswordLength {
			return fmt.Errorf("channel password too long: %d > %d", len(password[0]), MaxChannelPasswordLength)
		}
		channel.password = password[0]
	}

	cm.channels[strings.ToLower(channelName)] = channel
	return nil
}

// IsInChannel checks if a character is in the specified channel
func (cm *ChatManager) IsInChannel(characterID int32, channelName string) bool {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	if channel, exists := cm.channels[strings.ToLower(channelName)]; exists {
		return channel.isInChannel(characterID)
	}
	return false
}

// JoinChannel adds a character to a channel
func (cm *ChatManager) JoinChannel(characterID int32, channelName string, password ...string) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	channel, exists := cm.channels[strings.ToLower(channelName)]
	if !exists {
		return fmt.Errorf("channel %s does not exist", channelName)
	}

	// Check password if channel has one
	if channel.password != "" {
		if len(password) == 0 || password[0] != channel.password {
			return fmt.Errorf("invalid password for channel %s", channelName)
		}
	}

	// Get player info for validation
	playerInfo, err := cm.playerManager.GetPlayerInfo(characterID)
	if err != nil {
		return fmt.Errorf("failed to get player info: %w", err)
	}

	// Validate restrictions
	if !cm.canJoinChannel(playerInfo.Level, playerInfo.Race, playerInfo.Class,
		channel.levelRestriction, channel.raceRestriction, channel.classRestriction) {
		return fmt.Errorf("player does not meet channel requirements")
	}

	// Add to channel
	if err := channel.joinChannel(characterID); err != nil {
		return err
	}

	// Notify all channel members of the join
	cm.notifyChannelUpdate(channelName, ChatChannelOtherJoin, playerInfo.CharacterName, characterID)

	return nil
}

// LeaveChannel removes a character from a channel
func (cm *ChatManager) LeaveChannel(characterID int32, channelName string) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	channel, exists := cm.channels[strings.ToLower(channelName)]
	if !exists {
		return fmt.Errorf("channel %s does not exist", channelName)
	}

	// Get player info for notification
	playerInfo, err := cm.playerManager.GetPlayerInfo(characterID)
	if err != nil {
		return fmt.Errorf("failed to get player info: %w", err)
	}

	// Remove from channel
	if err := channel.leaveChannel(characterID); err != nil {
		return err
	}

	// Delete custom channels with no members
	if channel.channelType == ChannelTypeCustom && len(channel.members) == 0 {
		delete(cm.channels, strings.ToLower(channelName))
	}

	// Notify all remaining channel members of the leave
	cm.notifyChannelUpdate(channelName, ChatChannelOtherLeave, playerInfo.CharacterName, characterID)

	return nil
}

// LeaveAllChannels removes a character from all channels they're in
func (cm *ChatManager) LeaveAllChannels(characterID int32) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	playerInfo, err := cm.playerManager.GetPlayerInfo(characterID)
	if err != nil {
		return fmt.Errorf("failed to get player info: %w", err)
	}

	// Find all channels the player is in and remove them
	var channelsToDelete []string
	for channelName, channel := range cm.channels {
		if channel.isInChannel(characterID) {
			channel.leaveChannel(characterID)

			// Mark custom channels with no members for deletion
			if channel.channelType == ChannelTypeCustom && len(channel.members) == 0 {
				channelsToDelete = append(channelsToDelete, channelName)
			} else {
				// Notify remaining members
				cm.notifyChannelUpdate(channel.name, ChatChannelOtherLeave, playerInfo.CharacterName, characterID)
			}
		}
	}

	// Delete empty custom channels
	for _, channelName := range channelsToDelete {
		delete(cm.channels, channelName)
	}

	return nil
}

// TellChannel sends a message to all members of a channel
func (cm *ChatManager) TellChannel(senderID int32, channelName, message string, customName ...string) error {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	channel, exists := cm.channels[strings.ToLower(channelName)]
	if !exists {
		return fmt.Errorf("channel %s does not exist", channelName)
	}

	// Check if sender is in channel (unless it's a system message)
	if senderID != 0 && !channel.isInChannel(senderID) {
		return fmt.Errorf("sender is not in channel %s", channelName)
	}

	// Get sender info
	var senderName string
	var languageID int32

	if senderID != 0 {
		playerInfo, err := cm.playerManager.GetPlayerInfo(senderID)
		if err != nil {
			return fmt.Errorf("failed to get sender info: %w", err)
		}
		senderName = playerInfo.CharacterName

		// Get sender's default language
		if cm.languageProcessor != nil {
			languageID = cm.languageProcessor.GetDefaultLanguage(senderID)
		}
	}

	// Use custom name if provided (for system messages)
	if len(customName) > 0 && customName[0] != "" {
		senderName = customName[0]
	}

	// Create message
	chatMessage := ChannelMessage{
		SenderID:    senderID,
		SenderName:  senderName,
		Message:     message,
		LanguageID:  languageID,
		ChannelName: channelName,
		Timestamp:   time.Now(),
	}

	// Send to all channel members
	return cm.deliverChannelMessage(channel, chatMessage)
}

// SendChannelUserList sends the list of users in a channel to a client
func (cm *ChatManager) SendChannelUserList(requesterID int32, channelName string) error {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	channel, exists := cm.channels[strings.ToLower(channelName)]
	if !exists {
		return fmt.Errorf("channel %s does not exist", channelName)
	}

	// Check if requester is in channel
	if !channel.isInChannel(requesterID) {
		return fmt.Errorf("requester is not in channel %s", channelName)
	}

	// Build member list
	var members []ChannelMember
	for _, memberID := range channel.members {
		if playerInfo, err := cm.playerManager.GetPlayerInfo(memberID); err == nil {
			member := ChannelMember{
				CharacterID:   memberID,
				CharacterName: playerInfo.CharacterName,
				Level:         playerInfo.Level,
				Race:          playerInfo.Race,
				Class:         playerInfo.Class,
				JoinedAt:      time.Now(), // TODO: Track actual join time
			}
			members = append(members, member)
		}
	}

	// Send user list to requester
	return cm.clientManager.SendChannelUserList(requesterID, channelName, members)
}

// GetChannel returns a channel by name (for internal use)
func (cm *ChatManager) GetChannel(channelName string) *Channel {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	return cm.channels[strings.ToLower(channelName)]
}

// GetStatistics returns chat system statistics
func (cm *ChatManager) GetStatistics() ChatStatistics {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	stats := ChatStatistics{
		TotalChannels: len(cm.channels),
	}

	for _, channel := range cm.channels {
		switch channel.channelType {
		case ChannelTypeWorld:
			stats.WorldChannels++
		case ChannelTypeCustom:
			stats.CustomChannels++
		}

		stats.TotalMembers += len(channel.members)
		if len(channel.members) > 0 {
			stats.ActiveChannels++
		}
	}

	return stats
}

// Helper methods

// canJoinChannel checks if a player meets channel requirements
func (cm *ChatManager) canJoinChannel(playerLevel, playerRace, playerClass, levelReq, raceReq, classReq int32) bool {
	// Check level restriction
	if levelReq > NoLevelRestriction && playerLevel < levelReq {
		return false
	}

	// Check race restriction (bitmask)
	if raceReq > NoRaceRestriction && (raceReq&(1<<playerRace)) == 0 {
		return false
	}

	// Check class restriction (bitmask)
	if classReq > NoClassRestriction && (classReq&(1<<playerClass)) == 0 {
		return false
	}

	return true
}

// notifyChannelUpdate sends channel update notifications to all members
func (cm *ChatManager) notifyChannelUpdate(channelName string, action int, characterName string, excludeCharacterID int32) {
	if channel, exists := cm.channels[strings.ToLower(channelName)]; exists {
		for _, memberID := range channel.members {
			if memberID != excludeCharacterID {
				cm.clientManager.SendChannelUpdate(memberID, channelName, action, characterName)
			}
		}
	}
}

// deliverChannelMessage processes and delivers a message to all channel members
func (cm *ChatManager) deliverChannelMessage(channel *Channel, message ChannelMessage) error {
	for _, memberID := range channel.members {
		// Process message for language if language processor is available
		processedMessage := message
		if cm.languageProcessor != nil && message.SenderID != 0 {
			if processedText, err := cm.languageProcessor.ProcessMessage(
				message.SenderID, memberID, message.Message, message.LanguageID); err == nil {
				processedMessage.Message = processedText
			}
		}

		// Send message to member
		if err := cm.clientManager.SendChannelMessage(memberID, processedMessage); err != nil {
			// Log error but continue sending to other members
			// TODO: Add proper logging
		}
	}

	return nil
}
