package game

import (
	"fmt"
	"image/color"
	"math/rand"
	"os"
	"time"

	"tetris/internal/font"
	"tetris/internal/i18n"
	"tetris/internal/tetromino"
	"tetris/internal/types"
	"tetris/pkg/config"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
	"github.com/hajimehoshi/ebiten/v2/vector"
)

// Game represents the main game state
type Game struct {
	board           [][]types.BlockType
	currentPiece    *tetromino.Tetromino
	nextPiece       *tetromino.Tetromino
	dropCounter     int
	dropInterval    int
	score           int
	level           int
	gameOver        bool
	lastMoveDown    time.Time
	lastRotate      time.Time
	lastMoveLeft    time.Time
	lastMoveRight   time.Time
	fontRenderer    *font.FontRenderer
	hardDropping    bool
	hardDropCounter int
}

// New creates a new game instance
func New() *Game {
	g := &Game{
		board:         make([][]types.BlockType, config.BoardHeight),
		dropInterval:  config.InitialDropInterval,
		lastMoveDown:  time.Now(),
		lastRotate:    time.Now(),
		lastMoveLeft:  time.Now(),
		lastMoveRight: time.Now(),
		fontRenderer:  font.NewFontRenderer(),
	}

	for i := range g.board {
		g.board[i] = make([]types.BlockType, config.BoardWidth)
	}

	g.spawnNewPiece()
	return g
}

// Update handles game logic updates
func (g *Game) Update() error {
	if g.gameOver {
		if inpututil.IsKeyJustPressed(ebiten.KeyR) {
			*g = *New()
			return nil
		}
		return nil
	}

	g.handleInput()
	g.updateDropCounter()
	return nil
}

// Draw renders the game state
func (g *Game) Draw(screen *ebiten.Image) {
	// Draw board background
	vector.DrawFilledRect(screen, 0, 0, float32(config.BoardWidth*config.BlockSize), float32(config.BoardHeight*config.BlockSize), color.RGBA{40, 40, 40, 255}, false)

	// Draw preview area background
	vector.DrawFilledRect(screen, float32(config.PreviewX-config.BlockSize), 0, float32(6*config.BlockSize), float32(config.ScreenHeight), color.RGBA{30, 30, 30, 255}, false)

	// Draw grid lines
	gridColor := color.RGBA{60, 60, 60, 255}
	for x := 0; x <= config.BoardWidth; x++ {
		vector.StrokeLine(screen, float32(x*config.BlockSize), 0, float32(x*config.BlockSize), float32(config.BoardHeight*config.BlockSize), 1, gridColor, false)
	}
	for y := 0; y <= config.BoardHeight; y++ {
		vector.StrokeLine(screen, 0, float32(y*config.BlockSize), float32(config.BoardWidth*config.BlockSize), float32(y*config.BlockSize), 1, gridColor, false)
	}

	// Draw placed blocks
	for y := range config.BoardHeight {
		for x := range config.BoardWidth {
			if g.board[y][x] != config.EmptyBlock {
				g.drawBlock(screen, x, y, g.board[y][x])
			}
		}
	}

	// Draw current piece and its ghost
	if g.currentPiece != nil {
		// Draw ghost piece
		ghostPiece := *g.currentPiece
		for {
			ghostPiece.Move(0, 1)
			if g.wouldCollide(&ghostPiece) {
				ghostPiece.Move(0, -1)
				break
			}
		}
		for _, pos := range ghostPiece.GetAbsolutePositions() {
			if pos[1] >= 0 {
				ghostColor := color.RGBA{128, 128, 128, 128}
				vector.DrawFilledRect(screen,
					float32(pos[0]*config.BlockSize),
					float32(pos[1]*config.BlockSize),
					float32(config.BlockSize-1),
					float32(config.BlockSize-1),
					ghostColor, false)
			}
		}

		// Draw actual piece with special effect during hard drop
		for _, pos := range g.currentPiece.GetAbsolutePositions() {
			if pos[1] >= 0 {
				if g.hardDropping {
					g.drawHardDropBlock(screen, pos[0], pos[1], g.currentPiece.BlockType)
				} else {
					g.drawBlock(screen, pos[0], pos[1], g.currentPiece.BlockType)
				}
			}
		}
	}

	// Draw next piece preview
	if g.nextPiece != nil {
		text := i18n.GetText()
		g.fontRenderer.DrawText(screen, text.Next, float64(config.PreviewX-config.BlockSize), float64(config.BlockSize), color.RGBA{255, 255, 255, 255})

		for _, block := range g.nextPiece.Blocks {
			x := config.PreviewX + block.X*config.BlockSize
			y := config.PreviewY + block.Y*config.BlockSize
			vector.DrawFilledRect(screen,
				float32(x),
				float32(y),
				float32(config.BlockSize-1),
				float32(config.BlockSize-1),
				types.BlockColors[g.nextPiece.BlockType], false)
		}
	}

	// Draw score and level with larger font
	text := i18n.GetText()
	scoreStr := fmt.Sprintf(text.Score, g.score)
	levelStr := fmt.Sprintf(text.Level, g.level)
	g.fontRenderer.DrawText(screen, scoreStr, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight-4*config.BlockSize), color.RGBA{255, 255, 255, 255})
	g.fontRenderer.DrawText(screen, levelStr, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight-3*config.BlockSize), color.RGBA{255, 255, 255, 255})

	// Draw controls instructions
	g.drawControls(screen)

	if g.gameOver {
		text := i18n.GetText()
		g.fontRenderer.DrawText(screen, text.GameOver, float64(config.ScreenWidth/2-20), float64(config.ScreenHeight/2-100), color.RGBA{255, 255, 255, 255})
		g.fontRenderer.DrawText(screen, text.Restart, float64(config.ScreenWidth/2-20), float64(config.ScreenHeight/2-80), color.RGBA{255, 0, 0, 255})
	}
}

// Layout implements ebiten.Game interface
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return config.ScreenWidth, config.ScreenHeight
}

// handleInput processes player input
func (g *Game) handleInput() {
	// Language switch
	if inpututil.IsKeyJustPressed(ebiten.KeyL) {
		i18n.SwitchLanguage()
	}

	// Restart game anytime
	if inpututil.IsKeyJustPressed(ebiten.KeyR) {
		g.restart()
		return
	}

	// Skip other inputs when game over
	if g.gameOver {
		return
	}

	// Hard drop
	if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
		g.hardDrop()
	}

	// Skip movement inputs during hard drop animation
	if g.hardDropping {
		return
	}

	// Move left/right - support continuous movement when holding keys
	if ebiten.IsKeyPressed(ebiten.KeyLeft) {
		// First press moves immediately, then add delay for continuous movement
		if inpututil.IsKeyJustPressed(ebiten.KeyLeft) {
			g.tryMove(-1, 0)
			g.lastMoveLeft = time.Now()
		} else if time.Since(g.lastMoveLeft) > time.Millisecond*120 {
			g.tryMove(-1, 0)
			g.lastMoveLeft = time.Now()
		}
	}

	if ebiten.IsKeyPressed(ebiten.KeyRight) {
		// First press moves immediately, then add delay for continuous movement
		if inpututil.IsKeyJustPressed(ebiten.KeyRight) {
			g.tryMove(1, 0)
			g.lastMoveRight = time.Now()
		} else if time.Since(g.lastMoveRight) > time.Millisecond*120 {
			g.tryMove(1, 0)
			g.lastMoveRight = time.Now()
		}
	}

	// Rotate
	if inpututil.IsKeyJustPressed(ebiten.KeyUp) {
		if time.Since(g.lastRotate) > time.Millisecond*100 {
			g.tryRotate()
			g.lastRotate = time.Now()
		}
	}

	// Soft drop
	if ebiten.IsKeyPressed(ebiten.KeyDown) {
		if time.Since(g.lastMoveDown) > time.Millisecond*50 {
			g.tryMove(0, 1)
			g.lastMoveDown = time.Now()
		}
	}
	// Exit game
	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		os.Exit(0)
	}
}

// updateDropCounter handles automatic piece dropping and hard drop animation
func (g *Game) updateDropCounter() {
	if g.hardDropping {
		// Hard drop animation - move faster
		g.hardDropCounter++
		if g.hardDropCounter >= 2 { // Move every 2 frames for visible animation
			g.hardDropCounter = 0
			if !g.tryMove(0, 1) {
				g.hardDropping = false
				g.lockPiece()
			}
		}
	} else {
		// Normal drop
		g.dropCounter++
		if g.dropCounter >= g.dropInterval {
			g.dropCounter = 0
			if !g.tryMove(0, 1) {
				g.lockPiece()
			}
		}
	}
}

// tryMove attempts to move the current piece and returns success
func (g *Game) tryMove(dx, dy int) bool {
	if g.currentPiece == nil {
		return false
	}

	originalX, originalY := g.currentPiece.X, g.currentPiece.Y
	g.currentPiece.Move(dx, dy)

	if g.isColliding() {
		g.currentPiece.X, g.currentPiece.Y = originalX, originalY
		return false
	}

	return true
}

// tryRotate attempts to rotate the current piece with wall kicks
func (g *Game) tryRotate() bool {
	if g.currentPiece == nil {
		return false
	}

	// Store original state
	originalBlocks := make([]types.Block, len(g.currentPiece.Blocks))
	copy(originalBlocks, g.currentPiece.Blocks)

	// Try rotation
	g.currentPiece.Rotate()

	// Try wall kicks
	var kicks [][2]int
	if g.currentPiece.BlockType == types.IBlock {
		kicks = tetromino.IKicks
	} else {
		kicks = tetromino.NormalKicks
	}

	for _, kick := range kicks {
		originalX, originalY := g.currentPiece.X, g.currentPiece.Y
		g.currentPiece.Move(kick[0], kick[1])

		if !g.isColliding() {
			return true
		}

		// Reset position for next kick attempt
		g.currentPiece.X, g.currentPiece.Y = originalX, originalY
	}

	// All kicks failed, revert rotation
	g.currentPiece.Blocks = originalBlocks
	return false
}

// hardDrop starts the hard drop animation
func (g *Game) hardDrop() {
	if g.currentPiece == nil || g.hardDropping {
		return
	}

	g.hardDropping = true
	g.hardDropCounter = 0
}

// isColliding checks if the current piece is colliding with the board or boundaries
func (g *Game) isColliding() bool {
	if g.currentPiece == nil {
		return false
	}

	for _, pos := range g.currentPiece.GetAbsolutePositions() {
		x, y := pos[0], pos[1]

		// Check boundaries
		if x < 0 || x >= config.BoardWidth || y >= config.BoardHeight {
			return true
		}

		// Check collision with placed blocks (but allow pieces above the visible board)
		if y >= 0 && g.board[y][x] != config.EmptyBlock {
			return true
		}
	}

	return false
}

// lockPiece places the current piece on the board and spawns a new one
func (g *Game) lockPiece() {
	if g.currentPiece == nil {
		return
	}

	// Place the piece on the board
	for _, pos := range g.currentPiece.GetAbsolutePositions() {
		x, y := pos[0], pos[1]
		if y >= 0 && y < config.BoardHeight && x >= 0 && x < config.BoardWidth {
			g.board[y][x] = g.currentPiece.BlockType
		}
	}

	// Clear completed lines
	linesCleared := g.clearLines()

	// Update score and level
	g.updateScore(linesCleared)

	// Spawn new piece
	g.spawnNewPiece()

	// Check for game over
	if g.isColliding() {
		g.gameOver = true
	}
}

// clearLines removes completed lines and returns the number cleared
func (g *Game) clearLines() int {
	linesCleared := 0

	for y := config.BoardHeight - 1; y >= 0; y-- {
		if g.isLineFull(y) {
			g.removeLine(y)
			linesCleared++
			y++ // Check the same line again since everything moved down
		}
	}

	return linesCleared
}

// updateScore updates the score and level based on lines cleared
func (g *Game) updateScore(linesCleared int) {
	points := []int{0, 40, 100, 300, 1200} // Points for 0, 1, 2, 3, 4 lines
	if linesCleared <= 4 {
		g.score += points[linesCleared] * (g.level + 1)
	}

	// Increase level every 10 lines
	newLevel := g.score / 1000
	if newLevel > g.level {
		g.level = newLevel
		g.dropInterval = max(config.MinDropInterval, config.InitialDropInterval-g.level*3)
	}
}

// isLineFull checks if a line is completely filled
func (g *Game) isLineFull(y int) bool {
	for x := 0; x < config.BoardWidth; x++ {
		if g.board[y][x] == config.EmptyBlock {
			return false
		}
	}
	return true
}

// removeLine removes a line and shifts everything above down
func (g *Game) removeLine(lineY int) {
	for y := lineY; y > 0; y-- {
		copy(g.board[y], g.board[y-1])
	}
	// Clear the top line
	for x := 0; x < config.BoardWidth; x++ {
		g.board[0][x] = config.EmptyBlock
	}
}

// spawnNewPiece creates a new falling piece
func (g *Game) spawnNewPiece() {
	if g.nextPiece == nil {
		g.nextPiece = tetromino.New(types.BlockType(rand.Intn(7)+1), config.BoardWidth/2-1, 0)
	}

	g.currentPiece = g.nextPiece
	g.nextPiece = tetromino.New(types.BlockType(rand.Intn(7)+1), config.BoardWidth/2-1, 0)

	g.dropCounter = 0
}

// wouldCollide checks if a piece would collide at its current position
func (g *Game) wouldCollide(piece *tetromino.Tetromino) bool {
	for _, pos := range piece.GetAbsolutePositions() {
		x, y := pos[0], pos[1]

		// Check boundaries
		if x < 0 || x >= config.BoardWidth || y >= config.BoardHeight {
			return true
		}

		// Check collision with placed blocks
		if y >= 0 && g.board[y][x] != config.EmptyBlock {
			return true
		}
	}

	return false
}

// drawBlock draws a single block at the specified position
func (g *Game) drawBlock(screen *ebiten.Image, x, y int, blockType types.BlockType) {
	vector.DrawFilledRect(screen,
		float32(x*config.BlockSize),
		float32(y*config.BlockSize),
		float32(config.BlockSize-1),
		float32(config.BlockSize-1),
		types.BlockColors[blockType], false)

	// Add a subtle border for 3D effect
	borderColor := color.RGBA{255, 255, 255, 50}
	vector.DrawFilledRect(screen,
		float32(x*config.BlockSize),
		float32(y*config.BlockSize),
		float32(config.BlockSize-1),
		2,
		borderColor, false)
	vector.DrawFilledRect(screen,
		float32(x*config.BlockSize),
		float32(y*config.BlockSize),
		2,
		float32(config.BlockSize-1),
		borderColor, false)
}

// drawHardDropBlock draws a block with special hard drop effect
func (g *Game) drawHardDropBlock(screen *ebiten.Image, x, y int, blockType types.BlockType) {
	// Get original color and make it brighter for hard drop effect
	originalColor := types.BlockColors[blockType].(color.RGBA)
	hardDropColor := color.RGBA{
		min(255, originalColor.R+50),
		min(255, originalColor.G+50),
		min(255, originalColor.B+50),
		255,
	}

	vector.DrawFilledRect(screen,
		float32(x*config.BlockSize),
		float32(y*config.BlockSize),
		float32(config.BlockSize-1),
		float32(config.BlockSize-1),
		hardDropColor, false)

	// Add bright white border for hard drop effect
	borderColor := color.RGBA{255, 255, 255, 200}
	vector.DrawFilledRect(screen,
		float32(x*config.BlockSize),
		float32(y*config.BlockSize),
		float32(config.BlockSize-1),
		3,
		borderColor, false)
	vector.DrawFilledRect(screen,
		float32(x*config.BlockSize),
		float32(y*config.BlockSize),
		3,
		float32(config.BlockSize-1),
		borderColor, false)
}

// max returns the maximum of two integers
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// min returns the minimum of two uint8 values
func min(a, b uint8) uint8 {
	if a < b {
		return a
	}
	return b
}

// drawControls renders the control instructions on the right side of the screen
func (g *Game) drawControls(screen *ebiten.Image) {
	// Starting position for controls display
	controlsX := float64(config.PreviewX - config.BlockSize)
	controlsY := float64(config.BlockSize * 8) // Below the next piece preview

	text := i18n.GetText()

	// Controls title
	g.fontRenderer.DrawText(screen, text.Controls, controlsX, controlsY, color.RGBA{255, 255, 255, 255})
	controlsY += 25

	// Control instructions
	controls := []string{
		text.Move,
		text.Rotate,
		text.SoftDrop,
		text.HardDrop,
		text.PressE,
		"",
		text.Language,
		text.PressL,
	}

	for _, control := range controls {
		g.fontRenderer.DrawText(screen, control, controlsX, controlsY, color.RGBA{255, 255, 255, 255})
		controlsY += 18
	}
}

// restart restarts the game
func (g *Game) restart() {
	*g = *New()
}
