package server

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

	"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config"
	"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
	"github.com/nats-io/nats.go"
)

type ServerPlayer struct {
	ID           string
	Name         string
	X, Y         float64
	VX, VY       float64
	OnGround     bool
	InputX       float64 // -1 (Links), 0, 1 (Rechts)
	LastInputSeq uint32  // Letzte verarbeitete Input-Sequenz
	Score        int
	IsAlive      bool
	IsSpectator  bool
}

type Room struct {
	ID             string
	NC             *nats.Conn
	World          *game.World
	Mutex          sync.RWMutex
	Players        map[string]*ServerPlayer
	ActiveChunks   []game.ActiveChunk
	Colliders      []game.Collider
	Status         string // "LOBBY", "COUNTDOWN", "RUNNING", "GAMEOVER"
	GlobalScrollX  float64
	MapEndX        float64
	Countdown      int
	NextStart      time.Time
	HostID         string
	CollectedCoins map[string]bool // Key: "chunkID_objectIndex"
	ScoreAccum     float64         // Akkumulator für Distanz-Score

	stopChan chan struct{}

	// Cache für Spieler-Hitbox aus Assets
	pW, pH      float64
	pDrawOffX   float64
	pDrawOffY   float64
	pHitboxOffX float64
	pHitboxOffY float64
}

// Konstruktor
func NewRoom(id string, nc *nats.Conn, w *game.World) *Room {
	r := &Room{
		ID:             id,
		NC:             nc,
		World:          w,
		Players:        make(map[string]*ServerPlayer),
		Status:         "LOBBY",
		stopChan:       make(chan struct{}),
		CollectedCoins: make(map[string]bool),
		pW:             40, pH: 60, // Fallback
	}

	// Player Werte aus Manifest laden
	if def, ok := w.Manifest.Assets["player"]; ok {
		r.pW = def.Hitbox.W
		r.pH = def.Hitbox.H
		r.pDrawOffX = def.DrawOffX
		r.pDrawOffY = def.DrawOffY
		r.pHitboxOffX = def.Hitbox.OffsetX
		r.pHitboxOffY = def.Hitbox.OffsetY
	}

	// Start-Chunk
	startChunk := game.ActiveChunk{ChunkID: "start", X: 0}
	r.ActiveChunks = append(r.ActiveChunks, startChunk)
	r.MapEndX = 1280

	// DEBUG: Verfügbare Chunks anzeigen
	log.Printf("🗂️  Raum %s: ChunkLibrary hat %d Einträge", id, len(w.ChunkLibrary))
	for cid := range w.ChunkLibrary {
		log.Printf("   - %s", cid)
	}

	// Erste Chunks generieren
	r.SpawnNextChunk()
	r.SpawnNextChunk()
	r.Colliders = r.World.GenerateColliders(r.ActiveChunks)

	log.Printf("🎬 Raum %s gestartet mit %d Chunks", id, len(r.ActiveChunks))
	for _, ac := range r.ActiveChunks {
		log.Printf("   - Chunk '%s' bei X=%.0f", ac.ChunkID, ac.X)
	}

	return r
}

// --- MAIN LOOP ---

func (r *Room) RunLoop() {
	// 60 Tick pro Sekunde
	ticker := time.NewTicker(time.Second / 60)
	defer ticker.Stop()

	for {
		select {
		case <-r.stopChan:
			return
		case <-ticker.C:
			r.Update()
			r.Broadcast()
		}
	}
}

// --- PLAYER MANAGEMENT ---

func (r *Room) AddPlayer(id, name string) {
	r.Mutex.Lock()
	defer r.Mutex.Unlock()

	if _, exists := r.Players[id]; exists {
		return
	}

	// Spawn-Position berechnen
	playerIndex := len(r.Players)
	spawnX := 100.0 + float64(playerIndex*150)

	p := &ServerPlayer{
		ID:          id,
		Name:        name,
		X:           spawnX,
		Y:           200,
		OnGround:    false,
		Score:       0,
		IsAlive:     true,
		IsSpectator: false,
	}

	// Falls das Spiel schon läuft, NICHT joinen erlauben
	if r.Status == "RUNNING" || r.Status == "COUNTDOWN" {
		// Spieler wird als Zuschauer hinzugefügt
		p.IsSpectator = true
		p.IsAlive = false
		log.Printf("⚠️ Spieler %s joined während des Spiels - wird Zuschauer", name)
	}

	r.Players[id] = p

	// Erster Spieler wird Host
	if r.HostID == "" {
		r.HostID = id

		// Auto-Start nur für Solo-Räume (erkennen am "solo_" Prefix)
		if len(r.ID) > 5 && r.ID[:5] == "solo_" {
			log.Printf("⏰ Solo-Mode: Auto-Start in 2 Sekunden für Raum %s", r.ID)
			go func() {
				time.Sleep(2 * time.Second)
				r.Mutex.Lock()
				if r.Status == "LOBBY" {
					r.Status = "COUNTDOWN"
					r.NextStart = time.Now().Add(3 * time.Second)
					log.Printf("🎮 Raum %s: Countdown gestartet!", r.ID)
				}
				r.Mutex.Unlock()
			}()
		}
	}
}

func (r *Room) ResetPlayer(p *ServerPlayer) {
	p.Y = 200
	p.X = r.GlobalScrollX + 200 // Sicherer Spawn
	p.VY = 0
	p.VX = 0
	p.OnGround = false
	log.Printf("♻️ RESET Player %s", p.Name)
}

// --- INPUT HANDLER ---

func (r *Room) HandleInput(input game.ClientInput) {
	r.Mutex.Lock()
	defer r.Mutex.Unlock()

	p, exists := r.Players[input.PlayerID]
	if !exists {
		return
	}

	// Sequenznummer aktualisieren
	if input.Sequence > p.LastInputSeq {
		p.LastInputSeq = input.Sequence
	}

	switch input.Type {
	case "JUMP":
		if p.OnGround {
			p.VY = -14.0
			p.OnGround = false
		}
	case "DOWN":
		p.VY = 15.0
	case "LEFT_DOWN":
		p.InputX = -1
	case "LEFT_UP":
		if p.InputX == -1 {
			p.InputX = 0
		}
	case "RIGHT_DOWN":
		p.InputX = 1
	case "RIGHT_UP":
		if p.InputX == 1 {
			p.InputX = 0
		}
	case "START":
		if input.PlayerID == r.HostID && r.Status == "LOBBY" {
			r.StartCountdown()
		}
	}
}

func (r *Room) StartCountdown() {
	r.Status = "COUNTDOWN"
	r.NextStart = time.Now().Add(3 * time.Second)
}

// --- PHYSIK & UPDATE ---

func (r *Room) Update() {
	r.Mutex.Lock()
	defer r.Mutex.Unlock()

	// 1. Status Logic
	if r.Status == "COUNTDOWN" {
		rem := time.Until(r.NextStart)
		r.Countdown = int(rem.Seconds()) + 1
		if rem <= 0 {
			r.Status = "RUNNING"
		}
	} else if r.Status == "RUNNING" {
		r.GlobalScrollX += config.RunSpeed
	}

	maxX := r.GlobalScrollX

	// 2. Spieler Physik
	for _, p := range r.Players {
		// Skip tote/Zuschauer Spieler
		if !p.IsAlive || p.IsSpectator {
			continue
		}

		// Lobby Mode
		if r.Status != "RUNNING" {
			p.VY += config.Gravity
			if p.Y > 540 {
				p.Y = 540
				p.VY = 0
				p.OnGround = true
			}
			if p.X < r.GlobalScrollX+50 {
				p.X = r.GlobalScrollX + 50
			}
			continue
		}

		// Prüfe aktuelle Position auf Obstacles (falls reingesprungen)
		currentHit, currentType := r.CheckCollision(p.X+r.pDrawOffX+r.pHitboxOffX, p.Y+r.pDrawOffY+r.pHitboxOffY, r.pW, r.pH)
		if currentHit && currentType == "obstacle" {
			r.KillPlayer(p)
			continue
		}

		// X Bewegung
		currentSpeed := config.RunSpeed + (p.InputX * 4.0)
		nextX := p.X + currentSpeed

		hitX, typeX := r.CheckCollision(nextX+r.pDrawOffX+r.pHitboxOffX, p.Y+r.pDrawOffY+r.pHitboxOffY, r.pW, r.pH)
		if hitX {
			if typeX == "obstacle" {
				r.KillPlayer(p)
				continue
			}
		} else {
			p.X = nextX
		}

		// Grenzen
		if p.X > r.GlobalScrollX+1200 {
			p.X = r.GlobalScrollX + 1200
		} // Rechts Block
		if p.X < r.GlobalScrollX-50 {
			r.KillPlayer(p)
			continue
		} // Links Tod

		if p.X > maxX {
			maxX = p.X
		}

		// Y Bewegung
		p.VY += config.Gravity
		if p.VY > config.MaxFall {
			p.VY = config.MaxFall
		}
		nextY := p.Y + p.VY

		hitY, typeY := r.CheckCollision(p.X+r.pDrawOffX+r.pHitboxOffX, nextY+r.pDrawOffY+r.pHitboxOffY, r.pW, r.pH)
		if hitY {
			if typeY == "obstacle" {
				r.KillPlayer(p)
				continue
			}
			if p.VY > 0 {
				p.OnGround = true
			}
			p.VY = 0
		} else {
			p.Y += p.VY
			p.OnGround = false
		}

		if p.Y > 1000 {
			r.KillPlayer(p)
		}

		// Coin Kollision prüfen
		r.CheckCoinCollision(p)
	}

	// 2b. Distanz-Score updaten
	if r.Status == "RUNNING" {
		r.UpdateDistanceScore()
	}

	// 3. Map Management
	r.UpdateMapLogic(maxX)

	// 4. Host Check
	if _, ok := r.Players[r.HostID]; !ok && len(r.Players) > 0 {
		for id := range r.Players {
			r.HostID = id
			break
		}
	}
}

// --- COLLISION & MAP ---

func (r *Room) CheckCollision(x, y, w, h float64) (bool, string) {
	playerRect := game.Rect{OffsetX: x, OffsetY: y, W: w, H: h}
	for _, c := range r.Colliders {
		if game.CheckRectCollision(playerRect, c.Rect) {
			if c.Type == "obstacle" {
				log.Printf("🔴 OBSTACLE HIT! Player: (%.0f, %.0f, %.0f, %.0f), Obstacle: (%.0f, %.0f, %.0f, %.0f)",
					playerRect.OffsetX, playerRect.OffsetY, playerRect.W, playerRect.H,
					c.Rect.OffsetX, c.Rect.OffsetY, c.Rect.W, c.Rect.H)
			}
			return true, c.Type
		}
	}
	return false, ""
}

func (r *Room) UpdateMapLogic(maxX float64) {
	if r.Status != "RUNNING" {
		return
	}

	// Neue Chunks spawnen
	if maxX > r.MapEndX-2000 {
		r.SpawnNextChunk()
		r.Colliders = r.World.GenerateColliders(r.ActiveChunks)
	}

	// Alte Chunks löschen
	if len(r.ActiveChunks) > 0 {
		firstChunk := r.ActiveChunks[0]
		chunkDef := r.World.ChunkLibrary[firstChunk.ChunkID]
		chunkWidth := float64(chunkDef.Width * config.TileSize)

		if firstChunk.X+chunkWidth < r.GlobalScrollX-1000 {
			r.ActiveChunks = r.ActiveChunks[1:]
			r.Colliders = r.World.GenerateColliders(r.ActiveChunks)
		}
	}
}

func (r *Room) SpawnNextChunk() {
	keys := make([]string, 0, len(r.World.ChunkLibrary))
	for k := range r.World.ChunkLibrary {
		keys = append(keys, k)
	}

	if len(keys) > 0 {
		// Zufälligen Chunk wählen
		randomID := keys[rand.Intn(len(keys))]
		chunkDef := r.World.ChunkLibrary[randomID]

		newChunk := game.ActiveChunk{ChunkID: randomID, X: r.MapEndX}
		r.ActiveChunks = append(r.ActiveChunks, newChunk)
		r.MapEndX += float64(chunkDef.Width * config.TileSize)
	} else {
		// Fallback, falls keine Chunks da sind
		r.MapEndX += 1280
	}
}

// --- NETZWERK ---

func (r *Room) Broadcast() {
	r.Mutex.RLock()
	defer r.Mutex.RUnlock()

	state := game.GameState{
		RoomID:         r.ID,
		Players:        make(map[string]game.PlayerState),
		Status:         r.Status,
		TimeLeft:       r.Countdown,
		WorldChunks:    r.ActiveChunks,
		HostID:         r.HostID,
		ScrollX:        r.GlobalScrollX,
		CollectedCoins: r.CollectedCoins,
	}

	for id, p := range r.Players {
		state.Players[id] = game.PlayerState{
			ID:           id,
			Name:         p.Name,
			X:            p.X,
			Y:            p.Y,
			VX:           p.VX,
			VY:           p.VY,
			OnGround:     p.OnGround,
			LastInputSeq: p.LastInputSeq,
			Score:        p.Score,
			IsAlive:      p.IsAlive,
			IsSpectator:  p.IsSpectator,
		}
	}

	// DEBUG: Ersten Broadcast loggen (nur beim ersten Mal)
	if len(r.Players) > 0 && r.Status == "LOBBY" {
		log.Printf("📡 Broadcast: Room=%s, Players=%d, Chunks=%d, Status=%s", r.ID, len(state.Players), len(state.WorldChunks), r.Status)
	}

	// Senden an raum-spezifischen Channel: "game.update.<ROOMID>"
	channel := "game.update." + r.ID
	ec, _ := nats.NewEncodedConn(r.NC, nats.JSON_ENCODER)
	ec.Publish(channel, state)
}

// RemovePlayer entfernt einen Spieler aus dem Raum
func (r *Room) RemovePlayer(id string) {
	r.Mutex.Lock()
	defer r.Mutex.Unlock()
	delete(r.Players, id)
	log.Printf("➖ Player %s left room %s", id, r.ID)
}
