package main

import (
	"fmt"
	"log"
	"math"
	"math/rand"
	"time"

	"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config"
	"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
)

// startOfflineGame initialisiert eine lokale Spielrunde ohne Server
func (g *Game) startOfflineGame() {
	g.resetForNewGame()
	g.isOffline = true
	g.connected = false // Explizit offline
	g.appState = StateGame

	// Initialen GameState lokal erstellen (mit Countdown)
	g.stateMutex.Lock()
	g.gameState = game.GameState{
		Status:            "COUNTDOWN",
		TimeLeft:          3,
		RoomID:            "offline_solo",
		Players:           make(map[string]game.PlayerState),
		WorldChunks:       []game.ActiveChunk{{ChunkID: "start", X: 0}},
		CurrentSpeed:      config.RunSpeed,
		DifficultyFactor:  0,
		CollectedCoins:    make(map[string]bool),
		CollectedPowerups: make(map[string]bool),
	}

	// Lokalen Spieler hinzufügen
	g.gameState.Players[g.playerName] = game.PlayerState{
		ID:      g.playerName,
		Name:    g.playerName,
		X:       100,
		Y:       200,
		IsAlive: true,
	}
	g.stateMutex.Unlock()

	g.offlineMovingPlatforms = nil

	// Initialer Chunk-Library Check
	if len(g.world.ChunkLibrary) == 0 {
		log.Println("⚠️  Warnung: Keine Chunks in Library geladen!")
	}

	// Startzeit für Countdown
	g.roundStartTime = time.Now().Add(3 * time.Second)
	g.predictedX = 100
	g.predictedY = 200
	g.currentSpeed = 0 // Stillstand während Countdown

	g.notifyGameStarted()
	log.Println("🕹️ Offline-Modus mit Countdown gestartet")
}

// updateOfflineLoop simuliert die Server-Logik lokal
func (g *Game) updateOfflineLoop() {
	if !g.isOffline || g.gameState.Status == "GAMEOVER" {
		return
	}

	g.stateMutex.Lock()
	defer g.stateMutex.Unlock()

	// 1. Status Logic (Countdown -> Running)
	if g.gameState.Status == "COUNTDOWN" {
		rem := time.Until(g.roundStartTime)
		g.gameState.TimeLeft = int(rem.Seconds()) + 1

		if rem <= 0 {
			log.Println("🚀 Offline: GO!")
			g.gameState.Status = "RUNNING"
			g.gameState.TimeLeft = 0
			g.audio.PlayMusic()
			// Reset roundStartTime auf den tatsächlichen Spielstart für Schwierigkeits-Skalierung
			g.roundStartTime = time.Now()
		}
		return // Während Countdown keine weitere Logik (kein Scrolling, etc.)
	}

	if g.gameState.Status != "RUNNING" {
		return
	}

	elapsed := time.Since(g.roundStartTime).Seconds()

	// 2. Schwierigkeit & Speed
	g.gameState.DifficultyFactor = elapsed / config.MaxDifficultySeconds
	if g.gameState.DifficultyFactor > 1.0 {
		g.gameState.DifficultyFactor = 1.0
	}

	speedIncrease := g.gameState.DifficultyFactor * g.gameState.DifficultyFactor * 18.0
	g.gameState.CurrentSpeed = config.RunSpeed + speedIncrease
	g.currentSpeed = g.gameState.CurrentSpeed

	// 3. Scrolling
	g.gameState.ScrollX += g.currentSpeed

	// 4. Chunks nachladen
	mapEnd := 0.0
	for _, c := range g.gameState.WorldChunks {
		chunkDef := g.world.ChunkLibrary[c.ChunkID]
		end := c.X + float64(chunkDef.Width*config.TileSize)
		if end > mapEnd {
			mapEnd = end
		}
	}

	if mapEnd < g.gameState.ScrollX+2500 {
		g.spawnOfflineChunk(mapEnd)
	}

	// 5. Entferne alte Chunks
	if len(g.gameState.WorldChunks) > 5 {
		if g.gameState.WorldChunks[0].X < g.gameState.ScrollX-2000 {
			// Bereinige auch Moving Platforms des alten Chunks
			oldChunkID := g.gameState.WorldChunks[0].ChunkID
			newPlats := g.offlineMovingPlatforms[:0]
			for _, p := range g.offlineMovingPlatforms {
				if p.ChunkID != oldChunkID {
					newPlats = append(newPlats, p)
				}
			}
			g.offlineMovingPlatforms = newPlats
			g.gameState.WorldChunks = g.gameState.WorldChunks[1:]
		}
	}

	// 6. Update Moving Platforms
	g.updateOfflineMovingPlatforms()

	// 7. Player State Update (Score, Powerups, Collisions)
	p, ok := g.gameState.Players[g.playerName]
	if ok && p.IsAlive {
		// Basis-Score aus Distanz
		p.Score = int(g.gameState.ScrollX / 10)

		// Synchronisiere Prediction-State zurück in GameState (für Rendering)
		p.X = g.predictedX
		p.Y = g.predictedY
		p.VX = g.predictedVX
		p.VY = g.predictedVY
		p.OnGround = g.predictedGround
		p.OnWall = g.predictedOnWall

		// Lokale Kollisionsprüfung für Coins/Powerups
		g.checkOfflineCollisions(&p)

		// Powerup-Timer herunterschalten
		now := time.Now()
		if p.HasGodMode && now.After(g.godModeEndTime) {
			p.HasGodMode = false
		}
		if p.HasMagnet && now.After(g.magnetEndTime) {
			p.HasMagnet = false
		}
		if g.predictedHasDoubleJump && now.After(g.doubleJumpEndTime) {
			g.predictedHasDoubleJump = false
			p.HasDoubleJump = false
		}

		g.gameState.Players[g.playerName] = p
	}

	// Synchronisiere Plattform-Positionen für Renderer
	syncPlats := make([]game.MovingPlatformSync, len(g.offlineMovingPlatforms))
	for i, p := range g.offlineMovingPlatforms {
		syncPlats[i] = game.MovingPlatformSync{
			ChunkID:   p.ChunkID,
			ObjectIdx: p.ObjectIdx,
			AssetID:   p.AssetID,
			X:         p.CurrentX,
			Y:         p.CurrentY,
		}
	}
	g.gameState.MovingPlatforms = syncPlats
}

func (g *Game) spawnOfflineChunk(atX float64) {
	var pool []string
	for id := range g.world.ChunkLibrary {
		if id != "start" {
			pool = append(pool, id)
		}
	}

	if len(pool) > 0 {
		randomID := pool[rand.Intn(len(pool))]
		g.gameState.WorldChunks = append(g.gameState.WorldChunks, game.ActiveChunk{
			ChunkID: randomID,
			X:       atX,
		})

		// Extrahiere Plattformen aus dem neuen Chunk
		chunkDef := g.world.ChunkLibrary[randomID]
		for i, obj := range chunkDef.Objects {
			asset, ok := g.world.Manifest.Assets[obj.AssetID]
			if !ok {
				continue
			}

			// Check ob es eine bewegende Plattform ist (entweder Typ oder explizite Daten)
			if obj.MovingPlatform != nil {
				mpData := obj.MovingPlatform
				p := &MovingPlatform{
					ChunkID:    randomID,
					ObjectIdx:  i,
					AssetID:    obj.AssetID,
					StartX:     atX + mpData.StartX,
					StartY:     mpData.StartY,
					EndX:       atX + mpData.EndX,
					EndY:       mpData.EndY,
					Speed:      mpData.Speed,
					Direction:  1.0,
					IsActive:   true,
					CurrentX:   atX + mpData.StartX,
					CurrentY:   mpData.StartY,
					HitboxW:    asset.Hitbox.W,
					HitboxH:    asset.Hitbox.H,
					DrawOffX:   asset.DrawOffX,
					DrawOffY:   asset.DrawOffY,
					HitboxOffX: asset.Hitbox.OffsetX,
					HitboxOffY: asset.Hitbox.OffsetY,
				}
				g.offlineMovingPlatforms = append(g.offlineMovingPlatforms, p)
			} else if asset.Type == "moving_platform" || asset.Type == "platform" {
				// Statische Plattform (oder Fallback)
				// Wir fügen sie NICHT zu offlineMovingPlatforms hinzu, da sie über
				// den statischen World-Collider Check in physics.go bereits erfasst wird.
				// (Vorausgesetzt der Typ ist "platform")
			}
		}
	}
}

func (g *Game) updateOfflineMovingPlatforms() {
	for _, p := range g.offlineMovingPlatforms {
		if !p.IsActive {
			continue
		}

		dx := p.EndX - p.StartX
		dy := p.EndY - p.StartY
		dist := math.Sqrt(dx*dx + dy*dy)
		if dist < 1 {
			continue
		}

		vx := (dx / dist) * (p.Speed / 20.0) * p.Direction
		vy := (dy / dist) * (p.Speed / 20.0) * p.Direction

		p.CurrentX += vx
		p.CurrentY += vy

		// Ziel erreicht? Umkehren.
		if p.Direction > 0 {
			dToEnd := math.Sqrt(math.Pow(p.CurrentX-p.EndX, 2) + math.Pow(p.CurrentY-p.EndY, 2))
			if dToEnd < (p.Speed / 20.0) {
				p.Direction = -1.0
			}
		} else {
			dToStart := math.Sqrt(math.Pow(p.CurrentX-p.StartX, 2) + math.Pow(p.CurrentY-p.StartY, 2))
			if dToStart < (p.Speed / 20.0) {
				p.Direction = 1.0
			}
		}
	}
}

func (g *Game) checkOfflineCollisions(p *game.PlayerState) {
	// Hitbox des Spielers (Welt-Koordinaten)
	pW, pH := 40.0, 60.0 // Default
	pOffX, pOffY := 0.0, 0.0
	pDrawX, pDrawY := 0.0, 0.0
	if def, ok := g.world.Manifest.Assets["player"]; ok {
		pW = def.Hitbox.W
		pH = def.Hitbox.H
		pOffX = def.Hitbox.OffsetX
		pOffY = def.Hitbox.OffsetY
		pDrawX = def.DrawOffX
		pDrawY = def.DrawOffY
	}

	pRect := game.Rect{
		OffsetX: p.X + pDrawX + pOffX,
		OffsetY: p.Y + pDrawY + pOffY,
		W:       pW,
		H:       pH,
	}

	for _, ac := range g.gameState.WorldChunks {
		chunkDef := g.world.ChunkLibrary[ac.ChunkID]
		for i, obj := range chunkDef.Objects {
			asset, ok := g.world.Manifest.Assets[obj.AssetID]
			if !ok {
				continue
			}

			objID := fmt.Sprintf("%s_%d", ac.ChunkID, i)

			// 1. COINS
			if asset.Type == "coin" {
				if g.gameState.CollectedCoins[objID] {
					continue
				}

				coinX := ac.X + obj.X + asset.DrawOffX + asset.Hitbox.OffsetX
				coinY := obj.Y + asset.DrawOffY + asset.Hitbox.OffsetY

				// Magnet-Effekt?
				if p.HasMagnet {
					playerCenterX := pRect.OffsetX + pRect.W/2
					playerCenterY := pRect.OffsetY + pRect.H/2
					coinCenterX := coinX + asset.Hitbox.W/2
					coinCenterY := coinY + asset.Hitbox.H/2

					dist := math.Sqrt(math.Pow(playerCenterX-coinCenterX, 2) + math.Pow(playerCenterY-coinCenterY, 2))
					if dist < 300 {
						// Münze wird eingesammelt wenn im Magnet-Radius
						g.gameState.CollectedCoins[objID] = true
						p.Score += 200 // Bonus direkt auf Score
						g.audio.PlayCoin()
						continue
					}
				}

				coinRect := game.Rect{OffsetX: coinX, OffsetY: coinY, W: asset.Hitbox.W, H: asset.Hitbox.H}
				if game.CheckRectCollision(pRect, coinRect) {
					g.gameState.CollectedCoins[objID] = true
					p.Score += 200
					g.audio.PlayCoin()
				}
			}

			// 2. POWERUPS
			if asset.Type == "powerup" {
				if g.gameState.CollectedPowerups[objID] {
					continue
				}

				puRect := game.Rect{
					OffsetX: ac.X + obj.X + asset.DrawOffX + asset.Hitbox.OffsetX,
					OffsetY: obj.Y + asset.DrawOffY + asset.Hitbox.OffsetY,
					W:       asset.Hitbox.W,
					H:       asset.Hitbox.H,
				}

				if game.CheckRectCollision(pRect, puRect) {
					g.gameState.CollectedPowerups[objID] = true
					g.audio.PlayPowerUp()

					switch obj.AssetID {
					case "jumpboost":
						p.HasDoubleJump = true
						p.DoubleJumpUsed = false
						g.predictedHasDoubleJump = true
						g.predictedDoubleJumpUsed = false
						g.doubleJumpEndTime = time.Now().Add(15 * time.Second)
					case "godmode":
						p.HasGodMode = true
						g.godModeEndTime = time.Now().Add(10 * time.Second)
					case "magnet":
						p.HasMagnet = true
						g.magnetEndTime = time.Now().Add(8 * time.Second)
					}
				}
			}
		}
	}
}
