package game

import (
	"strings"
	"time"
)

// TypingStats holds the statistics for a game session
type TypingStats struct {
	WPM               float64
	Accuracy          float64
	CharactersTyped   int
	CorrectChars      int
	TotalChars        int
	TimeElapsed       time.Duration
	IsComplete        bool
	UncorrectedErrors int
}

// TypingGame represents the state of a game session
type TypingGame struct {
	AllWords        []string
	DisplayLines    []string
	UserInput       string
	CurrentPos      int
	GlobalPos       int
	StartTime       time.Time
	Duration        int
	IsStarted       bool
	IsFinished      bool
	Errors          map[int]bool
	TotalErrorsMade int
	LinesPerView    int
	CharsPerLine    int
	WordsTyped      int
}

// NewTypingGame initializes a new TypingGame instance with a specified duration
func NewTypingGame(duration int) *TypingGame {
	game := &TypingGame{
		AllWords:     GenerateWords(200),
		Duration:     duration,
		Errors:       make(map[int]bool),
		LinesPerView: 3,
		CharsPerLine: 50,
	}
	game.generateDisplayLines()
	return game
}

// Reset reinitializes the game to a fresh state
func (g *TypingGame) Reset() {
	*g = *NewTypingGame(g.Duration)
}

// generateDisplayLines creates the initial display lines based on the words available
func (g *TypingGame) generateDisplayLines() {
	lines := make([]string, 0, g.LinesPerView)
	wordIndex := g.WordsTyped

	// Generate exactly g.LinesPerView lines
	for lineNum := 0; lineNum < g.LinesPerView && wordIndex < len(g.AllWords); lineNum++ {
		var currentLine strings.Builder

		// Fill current line with words
		for wordIndex < len(g.AllWords) {
			word := g.AllWords[wordIndex]
			spaceNeeded := 0
			if currentLine.Len() > 0 {
				spaceNeeded = 1
			}

			// Check if word fits
			if currentLine.Len()+spaceNeeded+len(word) <= g.CharsPerLine {
				if currentLine.Len() > 0 {
					currentLine.WriteString(" ")
				}
				currentLine.WriteString(word)
				wordIndex++
			} else {
				// Word doesn't fit, break to next line
				break
			}
		}

		// Add the completed line
		if currentLine.Len() > 0 {
			lines = append(lines, currentLine.String())
		} else {
			// If no words fit, add empty line
			lines = append(lines, "")
		}
	}

	// Ensure we have exactly g.LinesPerView lines
	for len(lines) < g.LinesPerView {
		lines = append(lines, "")
	}

	// Truncate if somehow we have more than g.LinesPerView lines
	if len(lines) > g.LinesPerView {
		lines = lines[:g.LinesPerView]
	}

	g.DisplayLines = lines
}

// Start initializes the game session if it hasn't started yet
func (g *TypingGame) Start() {
	if !g.IsStarted {
		g.StartTime = time.Now()
		g.IsStarted = true
	}
}

// AddCharacter handles user input and updates game state
func (g *TypingGame) AddCharacter(char rune) {
	if !g.IsStarted {
		g.Start()
	}

	if g.IsFinished || g.IsTimeUp() {
		g.IsFinished = true
		return
	}

	lineText := []rune(g.DisplayLines[0])

	// If at end of line, only shift if user just typed space
	if g.CurrentPos == len(lineText) {
		if char == ' ' {
			g.UserInput += string(char)
			g.CurrentPos++
			g.GlobalPos++
			g.shiftLines()
		}
		return
	}

	// Normal character processing
	if g.CurrentPos < len(lineText) && g.CurrentPos >= 0 {
		g.UserInput += string(char)
		if lineText[g.CurrentPos] != char {
			g.Errors[g.GlobalPos] = true
			g.TotalErrorsMade++
		}
		g.CurrentPos++
		g.GlobalPos++
	}
}

// HandleEnterKey handles Enter key press for line progression
func (g *TypingGame) HandleEnterKey() bool {
	if g.IsFinished || g.IsTimeUp() {
		return false
	}

	lineText := []rune(g.DisplayLines[0])

	// Only allow Enter to progress if at end of line
	if g.CurrentPos == len(lineText) {
		// Treat Enter like Space internally for consistency
		g.UserInput += " "
		g.CurrentPos++
		g.GlobalPos++
		g.shiftLines()
		return true
	}

	return false
}

// shiftLines moves to the next line in the game, updating the words typed and generating new lines
func (g *TypingGame) shiftLines() {
	// Move to next line
	g.WordsTyped += len(strings.Fields(g.DisplayLines[0]))
	g.CurrentPos = 0

	// Generate new lines
	g.generateDisplayLines()

	// Extend words if needed
	if g.WordsTyped > len(g.AllWords)-50 {
		newWords := GenerateWords(100)
		g.AllWords = append(g.AllWords, newWords...)
	}
}

// RemoveCharacter removes the last character from the user input and updates the position
func (g *TypingGame) RemoveCharacter() {
	if len(g.UserInput) > 0 && g.CurrentPos > 0 {
		g.UserInput = g.UserInput[:len(g.UserInput)-1]
		g.CurrentPos--
		g.GlobalPos--

		// Remove error mark if previously added
		delete(g.Errors, g.GlobalPos)
	}
}

// GetDisplayText returns the current text to be displayed in the game
func (g *TypingGame) GetDisplayText() string {
	return strings.Join(g.DisplayLines, " ")
}

// GetStats calculates and returns the typing statistics for the current game session
func (g *TypingGame) GetStats() TypingStats {
	if !g.IsStarted {
		return TypingStats{}
	}

	elapsed := time.Since(g.StartTime)
	minutes := elapsed.Minutes()

	// Calculate Gross WPM (all typed entries / 5 / time in minutes)
	grossWPM := 0.0
	if minutes > 0 {
		grossWPM = float64(g.GlobalPos) / 5 / minutes
	}

	// Calculate uncorrected errors (errors still present in the text)
	uncorrectedErrors := len(g.Errors)

	// Calculate Net WPM (Gross WPM - uncorrected errors per minute)
	errorRate := float64(uncorrectedErrors) / minutes
	netWPM := grossWPM - errorRate

	// Ensure Net WPM doesn't go below 0
	if netWPM < 0 {
		netWPM = 0
	}

	// Calculate accuracy (correct characters / total characters typed * 100)
	correctChars := g.GlobalPos - g.TotalErrorsMade
	accuracy := 0.0
	if g.GlobalPos > 0 {
		accuracy = float64(correctChars) / float64(g.GlobalPos) * 100
	}

	// Ensure accuracy doesn't go below 0
	if accuracy < 0 {
		accuracy = 0
	}

	return TypingStats{
		WPM:               netWPM,
		Accuracy:          accuracy,
		CharactersTyped:   g.GlobalPos,
		CorrectChars:      correctChars,
		TotalChars:        len([]rune(g.GetDisplayText())),
		TimeElapsed:       elapsed,
		IsComplete:        g.IsFinished,
		UncorrectedErrors: uncorrectedErrors,
	}
}

// IsTimeUp checks if the game time has exceeded the specified duration
func (g *TypingGame) IsTimeUp() bool {
	if !g.IsStarted {
		return false
	}
	return time.Since(g.StartTime).Seconds() >= float64(g.Duration)
}

// GetRemainingTime returns the remaining time in seconds for the game
func (g *TypingGame) GetRemainingTime() int {
	if !g.IsStarted {
		return g.Duration
	}
	elapsed := int(time.Since(g.StartTime).Seconds())
	remaining := g.Duration - elapsed
	if remaining < 0 {
		return 0
	}
	return remaining
}
