package main

import (
	"log"
	"math"
	"time"

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

// CheckMovingPlatformLanding prüft ob der Spieler auf einer bewegenden Plattform steht
func (g *Game) CheckMovingPlatformLanding(x, y, w, h float64) *MovingPlatform {
	playerRect := game.Rect{OffsetX: x, OffsetY: y, W: w, H: h}

	for _, mp := range g.offlineMovingPlatforms {
		mpRect := game.Rect{
			OffsetX: mp.CurrentX + mp.DrawOffX + mp.HitboxOffX,
			OffsetY: mp.CurrentY + mp.DrawOffY + mp.HitboxOffY,
			W:       mp.HitboxW,
			H:       mp.HitboxH,
		}

		// Etwas großzügigerer Check nach oben
		if game.CheckRectCollision(playerRect, mpRect) {
			return mp
		}
	}
	return nil
}

// ApplyInput wendet einen Input auf den vorhergesagten Zustand an
// Nutzt die gemeinsame Physik-Engine aus pkg/physics
func (g *Game) ApplyInput(input InputState) {
	g.stateMutex.Lock()
	status := g.gameState.Status
	g.stateMutex.Unlock()

	if status == "COUNTDOWN" {
		return
	}

	// --- OFFLINE: Mit Plattform mitbewegen ---
	if g.isOffline && g.predictedGround {
		pConst := physics.DefaultPlayerConstants()
		mp := g.CheckMovingPlatformLanding(
			g.predictedX+pConst.DrawOffX+pConst.HitboxOffX,
			g.predictedY+pConst.DrawOffY+pConst.HitboxOffY,
			pConst.Width,
			pConst.Height,
		)
		if mp != nil {
			// Berechne Plattform-Geschwindigkeit
			dx := mp.EndX - mp.StartX
			dy := mp.EndY - mp.StartY
			dist := math.Sqrt(dx*dx + dy*dy)
			if dist > 0.1 {
				vx := (dx / dist) * (mp.Speed / 20.0) * mp.Direction
				vy := (dy / dist) * (mp.Speed / 20.0) * mp.Direction
				g.predictedX += vx
				g.predictedY += vy
			}
		}
	}

	// Horizontale Bewegung mit analogem Joystick
	moveX := 0.0
	if input.Left {
		moveX = -1.0
	} else if input.Right {
		moveX = 1.0
	}

	// Wenn Joystick benutzt wird, überschreibe moveX mit analogem Wert
	if input.JoyX != 0 {
		moveX = input.JoyX
	}

	// Physik-State vorbereiten
	state := physics.PlayerPhysicsState{
		X:        g.predictedX,
		Y:        g.predictedY,
		VX:       g.predictedVX,
		VY:       g.predictedVY,
		OnGround: g.predictedGround,
		OnWall:   g.predictedOnWall,
	}

	// Double Jump: vor der Physik anwenden (1:1 wie Server)
	if input.Jump && !g.predictedGround && g.predictedHasDoubleJump && !g.predictedDoubleJumpUsed {
		g.predictedVY = -config.JumpVelocity
		g.predictedDoubleJumpUsed = true
	}

	// Double Jump Reset wenn wieder am Boden
	if g.predictedGround {
		g.predictedDoubleJumpUsed = false
	}

	// Physik-Input vorbereiten
	physicsInput := physics.PhysicsInput{
		InputX: moveX,
		Jump:   input.Jump,
		Down:   input.Down,
	}

	// Kollisions-Checker vorbereiten
	g.stateMutex.Lock()
	collisionChecker := &physics.ClientCollisionChecker{
		World:           g.world,
		ActiveChunks:    g.gameState.WorldChunks,
		MovingPlatforms: g.gameState.MovingPlatforms,
	}
	difficultyFactor := g.gameState.DifficultyFactor
	g.stateMutex.Unlock()

	// Gemeinsame Physik anwenden (1:1 wie Server, inkl. Schwierigkeits-Skalierung)
	physics.ApplyPhysics(&state, physicsInput, g.currentSpeed, difficultyFactor, collisionChecker, physics.DefaultPlayerConstants())

	// Ergebnis zurückschreiben
	g.predictedX = state.X
	g.predictedY = state.Y
	g.predictedVX = state.VX
	g.predictedVY = state.VY
	g.predictedGround = state.OnGround
	g.predictedOnWall = state.OnWall
}

// ReconcileWithServer gleicht lokale Prediction mit Server-State ab
func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
	g.predictionMutex.Lock()
	defer g.predictionMutex.Unlock()

	// Server-bestätigte Sequenz
	g.lastServerSeq = serverState.LastInputSeq

	// Entferne alle bestätigten Inputs
	for seq := range g.pendingInputs {
		if seq <= g.lastServerSeq {
			delete(g.pendingInputs, seq)
		}
	}

	// Temporäre Position für Replay (jetzt MIT Y-Achse)
	replayX := serverState.X
	replayY := serverState.Y
	replayVX := serverState.VX
	replayVY := serverState.VY
	replayGround := serverState.OnGround
	replayOnWall := serverState.OnWall

	// Replay alle noch nicht bestätigten Inputs mit VOLLER Physik
	if len(g.pendingInputs) > 0 {
		for seq := g.lastServerSeq + 1; seq <= g.inputSequence; seq++ {
			if input, ok := g.pendingInputs[seq]; ok {
				// Temporär auf Replay-Position setzen
				oldX, oldY := g.predictedX, g.predictedY
				oldVX, oldVY := g.predictedVX, g.predictedVY
				oldGround := g.predictedGround
				oldOnWall := g.predictedOnWall

				g.predictedX = replayX
				g.predictedY = replayY
				g.predictedVX = replayVX
				g.predictedVY = replayVY
				g.predictedGround = replayGround
				g.predictedOnWall = replayOnWall

				g.ApplyInput(input)

				replayX = g.predictedX
				replayY = g.predictedY
				replayVX = g.predictedVX
				replayVY = g.predictedVY
				replayGround = g.predictedGround
				replayOnWall = g.predictedOnWall

				// Zurücksetzen
				g.predictedX = oldX
				g.predictedY = oldY
				g.predictedVX = oldVX
				g.predictedVY = oldVY
				g.predictedGround = oldGround
				g.predictedOnWall = oldOnWall
			}
		}
	}

	// Berechne Differenz zwischen Client-Prediction und Server-Replay (X und Y)
	diffX := replayX - g.predictedX
	diffY := replayY - g.predictedY
	dist := diffX*diffX + diffY*diffY

	// Speichere Korrektur-Magnitude für Debug
	g.correctionX = diffX
	g.correctionY = diffY

	// === SMOOTH CORRECTION MIT VISUELLEM OFFSET ===
	//
	// Strategie: Physics springt sofort zur richtigen Position (korrekte Physik),
	// aber der angezeigte Wert (predictedX/Y + correctionOffsetX/Y) springt NICHT –
	// er driftet sanft über ~80ms zur Physics-Position hin.
	//
	// Dadurch sehen Spieler keinen "Pushback/Teleport" mehr, selbst wenn der Server
	// eine größere Korrektur schickt.

	const (
		acceptTolerance   = 400.0  // ~20px: keine Korrektur
		criticalTolerance = 2500.0 // ~50px: sanfte Korrektur
	)

	if dist < acceptTolerance {
		// Kleine Abweichung (<20px): Client-Position beibehalten, kein Eingriff nötig

	} else if dist < criticalTolerance {
		// Mittlere Abweichung (20-50px): sanfte Physics-Korrektur (25%)
		// Visueller Offset kompensiert die Bewegung → kein sichtbarer Sprung
		factor := 0.25
		corrX := diffX * factor
		corrY := diffY * factor
		g.correctionOffsetX -= corrX // Offset gegenläufig zur Physics-Korrektur
		g.correctionOffsetY -= corrY
		g.predictedX += corrX
		g.predictedY += corrY
		g.correctionCount++

	} else {
		// Große Abweichung (>50px): Physics springt komplett zur Replay-Position.
		// Visueller Offset hält das Bild an der alten Stelle – Spieler sieht keinen Teleport.
		g.correctionOffsetX += g.predictedX - replayX // = -(replayX - predictedX) = -diffX
		g.correctionOffsetY += g.predictedY - replayY
		g.predictedX = replayX
		g.predictedY = replayY
		g.correctionCount++
	}

	// Velocity und Ground-State immer vom Replay übernehmen.
	// Replay-Werte kommen aus korrekter Server-State + Pending-Input-Replay
	// und sind physikalisch genauer als reine Client-Prediction über Zeit.
	g.predictedVX = replayVX
	g.predictedVY = replayVY
	g.predictedGround = replayGround
	g.predictedOnWall = replayOnWall

	// Powerup-State vom Server übernehmen (autoritativ)
	g.predictedHasDoubleJump = serverState.HasDoubleJump
	g.predictedDoubleJumpUsed = serverState.DoubleJumpUsed
}

// checkSoloRound führt lokale Prüfungen für den Solo-Modus durch.
// Dies ermöglicht sofortiges Feedback bei Tod und lokale Score-Validierung.
func (g *Game) checkSoloRound() {
	if g.gameMode != "solo" || g.gameState.Status != "RUNNING" {
		return
	}

	// 1. Lokale Todes-Erkennung (Nur noch Grenzen im Solo-Modus)
	g.stateMutex.Lock()
	scrollX := g.gameState.ScrollX
	g.stateMutex.Unlock()

	isDead := false
	deathReason := ""

	// Aus dem linken Bildschirmrand gefallen?
	if g.predictedX < scrollX-50 {
		isDead = true
		deathReason = "Vom Lehrer erwischt"
	}

	// Wenn lokal Tod festgestellt wurde, den GameState lokal auf GAMEOVER setzen
	// (Wird vom Server-Update später bestätigt, aber sorgt für 0ms Latenz im UI)
	if isDead {
		g.stateMutex.Lock()
		if g.gameState.Status == "RUNNING" {
			log.Printf("💀 Lokale Todes-Erkennung: %s! Beende Runde.", deathReason)
			g.gameState.Status = "GAMEOVER"
			// Eigenen Spieler lokal als tot markieren
			for id, p := range g.gameState.Players {
				if p.Name == g.playerName {
					p.IsAlive = false
					g.gameState.Players[id] = p
					break
				}
			}
			g.audio.StopMusic()
		}
		g.stateMutex.Unlock()
	}

	// 2. Lokale Score-Prüfung (Optional: Vergleiche mit Server)
	// In einem echten Anti-Cheat-Szenario könnte man hier die Distanz selbst tracken
}

// verifyRoundResult prüft am Ende der Runde die Konsistenz der Daten.
func (g *Game) verifyRoundResult() {
	g.stateMutex.Lock()
	defer g.stateMutex.Unlock()

	if g.gameMode != "solo" {
		return
	}

	myScore := 0
	for _, p := range g.gameState.Players {
		if p.Name == g.playerName {
			myScore = p.Score
			break
		}
	}

	duration := time.Since(g.roundStartTime).Seconds()
	log.Printf("🧐 Runde beendet. Überprüfe Ergebnis: %d Punkte (Dauer: %.1fs)", myScore, duration)

	// Hier könnten weitere Prüfungen folgen (z.B. war die Zeit plausibel?)
	// Für dieses Projekt zeigen wir die erfolgreiche Überprüfung im Log an.
}
