package game

import (
	"fmt"
	"log"
	"sync"
	"time"

	"gitea.boner.be/bdnugget/goonscape/types"
	pb "gitea.boner.be/bdnugget/goonserver/actions"
	rl "github.com/gen2brain/raylib-go/raylib"
)

// Local UI constants (these could be moved to a centralized constants package later)
const (
	runeLimit = 256
)

type Chat struct {
	messages     []types.ChatMessage
	inputBuffer  []rune
	isTyping     bool
	cursorPos    int
	scrollOffset int
	userData     interface{}
	mutex        sync.RWMutex
}

func NewChat() *Chat {
	return &Chat{
		messages:    make([]types.ChatMessage, 0, types.MaxChatMessages),
		inputBuffer: make([]rune, 0, runeLimit),
	}
}

func (c *Chat) AddMessage(playerID int32, content string) {
	msg := types.ChatMessage{
		PlayerID: playerID,
		Content:  content,
		Time:     time.Now(),
	}

	if len(c.messages) >= types.MaxChatMessages {
		c.messages = c.messages[1:]
	}
	c.messages = append(c.messages, msg)
	c.scrollOffset = 0 // Reset scroll position for new messages
}

func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	if len(messages) == 0 {
		return
	}

	log.Printf("Processing %d chat messages", len(messages))

	// Convert protobuf messages to our local type
	for _, msg := range messages {
		localMsg := types.ChatMessage{
			PlayerID: msg.PlayerId,
			Username: msg.Username,
			Content:  msg.Content,
			Time:     time.Unix(0, msg.Timestamp),
		}

		// Only add if it's not already in our history
		if len(c.messages) == 0 || c.messages[len(c.messages)-1].Time.UnixNano() < msg.Timestamp {
			if len(c.messages) >= types.MaxChatMessages {
				c.messages = c.messages[1:]
			}
			c.messages = append(c.messages, localMsg)
			log.Printf("Added chat message from %s: %s", msg.Username, msg.Content)

			// Scroll to latest message if it's not already visible
			visibleMessages := int((types.ChatHeight - types.InputHeight) / types.MessageHeight)
			if len(c.messages) > visibleMessages {
				c.scrollOffset = len(c.messages) - visibleMessages
			}

			// Add floating message to the player
			if game, ok := c.userData.(*Game); ok {
				if msg.PlayerId == game.Player.ID {
					game.Player.Lock()
					game.Player.FloatingMessage = &types.FloatingMessage{
						Content:    msg.Content,
						ExpireTime: time.Now().Add(6 * time.Second),
					}
					game.Player.Unlock()
				} else if otherPlayer, exists := game.OtherPlayers[msg.PlayerId]; exists {
					otherPlayer.Lock()
					otherPlayer.FloatingMessage = &types.FloatingMessage{
						Content:    msg.Content,
						ExpireTime: time.Now().Add(6 * time.Second),
					}
					otherPlayer.Unlock()
					log.Printf("Added floating message to other player %d", msg.PlayerId)
				} else {
					log.Printf("Could not find other player %d to add floating message", msg.PlayerId)
				}
			}
		}
	}
}

func (c *Chat) Draw(screenWidth, screenHeight int32) {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	// Calculate chat window width based on screen width
	chatWindowWidth := screenWidth - (types.ChatMargin * 2)

	// Draw chat window background
	chatX := float32(types.ChatMargin)
	chatY := float32(screenHeight - types.ChatHeight - types.ChatMargin)
	rl.DrawRectangle(int32(chatX), int32(chatY), chatWindowWidth, types.ChatHeight, rl.ColorAlpha(rl.Black, 0.5))

	// Draw messages from oldest to newest
	messageY := chatY + 5
	visibleMessages := int((types.ChatHeight - types.InputHeight) / types.MessageHeight)

	// Auto-scroll to bottom if no manual scrolling has occurred
	if c.scrollOffset == 0 {
		if len(c.messages) > visibleMessages {
			c.scrollOffset = len(c.messages) - visibleMessages
		}
	}

	startIdx := max(0, c.scrollOffset)
	endIdx := min(len(c.messages), startIdx+visibleMessages)

	for i := startIdx; i < endIdx; i++ {
		msg := c.messages[i]
		var color rl.Color
		if msg.PlayerID == 0 { // System message
			color = rl.Gold
		} else {
			color = rl.White
		}
		text := fmt.Sprintf("%s: %s", msg.Username, msg.Content)
		rl.DrawText(text, int32(chatX)+5, int32(messageY), 20, color)
		messageY += types.MessageHeight
	}

	// Draw input field
	inputY := chatY + float32(types.ChatHeight-types.InputHeight)
	rl.DrawRectangle(int32(chatX), int32(inputY), chatWindowWidth, types.InputHeight, rl.ColorAlpha(rl.White, 0.3))
	if c.isTyping {
		inputText := string(c.inputBuffer)
		rl.DrawText(inputText, int32(chatX)+5, int32(inputY)+5, 20, rl.White)

		// Draw cursor
		cursorX := rl.MeasureText(inputText[:c.cursorPos], 20)
		rl.DrawRectangle(int32(chatX)+5+cursorX, int32(inputY)+5, 2, 20, rl.White)
	} else {
		rl.DrawText("Press T to chat", int32(chatX)+5, int32(inputY)+5, 20, rl.Gray)
	}
}

func (c *Chat) Update() (string, bool) {
	// Handle scrolling with mouse wheel when not typing
	if !c.isTyping {
		wheelMove := rl.GetMouseWheelMove()
		if wheelMove != 0 {
			maxScroll := max(0, len(c.messages)-int((types.ChatHeight-types.InputHeight)/types.MessageHeight))
			c.scrollOffset = clamp(c.scrollOffset-int(wheelMove), 0, maxScroll)
		}

		if rl.IsKeyPressed(rl.KeyT) {
			c.isTyping = true
			return "", false
		}
		return "", false
	}

	key := rl.GetCharPressed()
	for key > 0 {
		if len(c.inputBuffer) < runeLimit {
			c.inputBuffer = append(c.inputBuffer[:c.cursorPos], append([]rune{key}, c.inputBuffer[c.cursorPos:]...)...)
			c.cursorPos++
		}
		key = rl.GetCharPressed()
	}

	if rl.IsKeyPressed(rl.KeyEnter) || rl.IsKeyPressed(rl.KeyKpEnter) {
		if len(c.inputBuffer) > 0 {
			message := string(c.inputBuffer)
			c.inputBuffer = c.inputBuffer[:0]
			c.cursorPos = 0
			c.isTyping = false
			return message, true
		}
		c.isTyping = false
	}

	if rl.IsKeyPressed(rl.KeyEscape) && c.isTyping {
		c.inputBuffer = c.inputBuffer[:0]
		c.cursorPos = 0
		c.isTyping = false
	}

	if rl.IsKeyPressed(rl.KeyBackspace) && c.cursorPos > 0 {
		c.inputBuffer = append(c.inputBuffer[:c.cursorPos-1], c.inputBuffer[c.cursorPos:]...)
		c.cursorPos--
	}

	if rl.IsKeyPressed(rl.KeyLeft) && c.cursorPos > 0 {
		c.cursorPos--
	}
	if rl.IsKeyPressed(rl.KeyRight) && c.cursorPos < len(c.inputBuffer) {
		c.cursorPos++
	}

	return "", false
}

// Add helper functions
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func clamp(value, min, max int) int {
	if value < min {
		return min
	}
	if value > max {
		return max
	}
	return value
}
