//go:build wasm
// +build wasm

package main

import (
	"encoding/json"
	"log"
	"syscall/js"
	"time"

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

// WebSocketMessage ist das Format für WebSocket-Nachrichten
type WebSocketMessage struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

// wsConn verwaltet die WebSocket-Verbindung im Browser
type wsConn struct {
	ws           js.Value
	messagesChan chan []byte
	connected    bool
}

// disconnectFromServer trennt die bestehende WebSocket-Verbindung sauber
func (g *Game) disconnectFromServer() {
	// Generation erhöhen → alle noch laufenden Handler der alten Verbindung werden ignoriert
	g.connGeneration++
	if g.wsConn != nil {
		g.wsConn.ws.Call("close")
		g.wsConn = nil
	}
	g.connected = false
}

// connectToServer verbindet sich über WebSocket mit dem Gateway
func (g *Game) connectToServer() {
	// Automatisch die richtige WebSocket-URL basierend auf der aktuellen Domain
	protocol := "ws:"
	if js.Global().Get("location").Get("protocol").String() == "https:" {
		protocol = "wss:"
	}
	host := js.Global().Get("location").Get("host").String()
	serverURL := protocol + "//" + host + "/ws"
	log.Printf("🔌 Verbinde zu WebSocket-Gateway: %s", serverURL)

	ws := js.Global().Get("WebSocket").New(serverURL)

	conn := &wsConn{
		ws:           ws,
		messagesChan: make(chan []byte, 100),
		connected:    false,
	}

	// Generation zum Zeitpunkt der Verbindung festhalten.
	// Wenn disconnectFromServer() aufgerufen wird, erhöht sich g.connGeneration
	// und alle Handler dieser alten Verbindung geben sofort nil zurück.
	myGen := g.connGeneration

	// OnOpen Handler
	ws.Call("addEventListener", "open", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if g.connGeneration != myGen {
			return nil // veraltete Verbindung
		}
		log.Println("✅ WebSocket verbunden!")
		conn.connected = true
		g.connected = true
		return nil
	}))

	// OnMessage Handler
	ws.Call("addEventListener", "message", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if g.connGeneration != myGen {
			return nil // veraltete Verbindung – Nachrichten ignorieren
		}
		if len(args) == 0 {
			return nil
		}

		data := args[0].Get("data").String()

		var msg WebSocketMessage
		if err := json.Unmarshal([]byte(data), &msg); err != nil {
			log.Printf("❌ Fehler beim Parsen der Nachricht: %v", err)
			return nil
		}

		switch msg.Type {
		case "game_update":
			// GameState Update
			payloadBytes, _ := json.Marshal(msg.Payload)
			var state game.GameState
			if err := json.Unmarshal(payloadBytes, &state); err == nil {
				// Out-of-Order-Erkennung: Ignoriere alte Updates
				if state.Sequence > 0 && state.Sequence <= g.lastRecvSeq {
					// Alte Nachricht - ignorieren
					g.outOfOrderCount++
					return nil
				}
				g.lastRecvSeq = state.Sequence
				g.totalUpdates++
				g.lastUpdateTime = time.Now()

				// Aktualisiere CurrentSpeed für Client-Prediction
				g.predictionMutex.Lock()
				g.currentSpeed = state.CurrentSpeed
				g.predictionMutex.Unlock()

				// Server Reconciliation für lokalen Spieler (VOR dem Lock)
				for _, p := range state.Players {
					if p.Name == g.playerName {
						g.ReconcileWithServer(p)
						break
					}
				}

				g.stateMutex.Lock()
				oldPlayerCount := len(g.gameState.Players)
				g.gameState = state
				newPlayerCount := len(g.gameState.Players)
				g.stateMutex.Unlock()

				// Lobby-UI aktualisieren wenn sich Spieleranzahl geändert hat
				if oldPlayerCount != newPlayerCount {
					g.sendLobbyUpdateToJS()
				}
			}

		case "leaderboard_response":
			// Leaderboard Response
			payloadBytes, _ := json.Marshal(msg.Payload)
			var resp game.LeaderboardResponse
			if err := json.Unmarshal(payloadBytes, &resp); err == nil {
				g.leaderboardMutex.Lock()
				g.leaderboard = resp.Entries
				g.leaderboardMutex.Unlock()
				log.Printf("📊 Leaderboard empfangen: %d Einträge", len(resp.Entries))

				// An JavaScript senden
				g.sendLeaderboardToJS()
			}

		case "score_response":
			// Score Submission Response mit Proof-Code
			payloadBytes, _ := json.Marshal(msg.Payload)
			var resp game.ScoreSubmissionResponse
			if err := json.Unmarshal(payloadBytes, &resp); err == nil {
				log.Printf("🎯 Proof-Code empfangen: %s für Score: %d", resp.ProofCode, resp.Score)

				// Proof-Code an JavaScript senden
				if saveFunc := js.Global().Get("saveHighscoreCode"); !saveFunc.IsUndefined() {
					saveFunc.Invoke(resp.Score, resp.ProofCode, g.playerName)
				}
			}
		}

		return nil
	}))

	// OnError Handler
	ws.Call("addEventListener", "error", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if g.connGeneration != myGen {
			return nil // veraltete Verbindung
		}
		log.Println("❌ WebSocket Fehler!")
		conn.connected = false
		g.connected = false
		return nil
	}))

	// OnClose Handler
	ws.Call("addEventListener", "close", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if g.connGeneration != myGen {
			return nil // veraltete Verbindung – kein g.connected überschreiben
		}
		log.Println("🔌 WebSocket geschlossen")
		conn.connected = false
		g.connected = false
		return nil
	}))

	// Warte bis Verbindung hergestellt ist
	for i := 0; i < 50; i++ {
		if conn.connected {
			break
		}
		time.Sleep(100 * time.Millisecond)
	}

	if !conn.connected {
		log.Println("❌ WebSocket-Verbindung Timeout")
		return
	}

	// WebSocket-Wrapper speichern
	g.wsConn = conn

	// JOIN senden
	joinMsg := WebSocketMessage{
		Type: "join",
		Payload: game.JoinRequest{
			Name:       g.playerName,
			RoomID:     g.roomID,
			GameMode:   g.gameMode,
			IsHost:     g.isHost,
			TeamName:   g.teamName,
			PlayerCode: g.playerCode,
		},
	}
	g.sendWebSocketMessage(joinMsg)

	log.Printf("➡️ JOIN gesendet über WebSocket: Name=%s, RoomID=%s, PlayerCode=%s", g.playerName, g.roomID, g.playerCode)
}

// sendWebSocketMessage sendet eine Nachricht über WebSocket
func (g *Game) sendWebSocketMessage(msg WebSocketMessage) {
	if g.wsConn == nil || !g.wsConn.connected {
		log.Println("⚠️ WebSocket nicht verbunden")
		return
	}

	data, err := json.Marshal(msg)
	if err != nil {
		log.Printf("❌ Fehler beim Marshallen: %v", err)
		return
	}

	g.wsConn.ws.Call("send", string(data))
}

// sendInput sendet einen Input über WebSocket
func (g *Game) sendInput(input game.ClientInput) {
	msg := WebSocketMessage{
		Type:    "input",
		Payload: input,
	}
	g.sendWebSocketMessage(msg)
}

// startGame sendet den Start-Befehl über WebSocket
func (g *Game) startGame() {
	// PlayerID ist der playerName (Server verwendet req.Name als PlayerID)
	myID := g.playerName
	msg := WebSocketMessage{
		Type: "start",
		Payload: game.StartRequest{
			RoomID:   g.roomID,
			PlayerID: myID,
		},
	}
	g.sendWebSocketMessage(msg)
	log.Printf("▶️ START gesendet über WebSocket: RoomID=%s, PlayerID=%s", g.roomID, myID)
}

// connectForLeaderboard verbindet für Leaderboard-Abfrage
func (g *Game) connectForLeaderboard() {
	if g.wsConn != nil && g.wsConn.connected {
		// Bereits verbunden
		g.requestLeaderboard()
		return
	}

	// Temporäre Daten für Leaderboard-Verbindung
	if g.playerName == "" {
		g.playerName = "LeaderboardViewer"
	}
	if g.roomID == "" {
		g.roomID = "leaderboard_only"
	}
	if g.gameMode == "" {
		g.gameMode = "solo"
	}

	// Neue Verbindung aufbauen
	g.connectToServer()

	// Kurz warten und dann Leaderboard anfragen
	time.Sleep(500 * time.Millisecond)
	g.requestLeaderboard()
}

// requestLeaderboard fordert das Leaderboard an
func (g *Game) requestLeaderboard() {
	// Verbindung aufbauen falls nicht vorhanden
	if g.wsConn == nil || !g.wsConn.connected {
		log.Println("📡 Keine Verbindung für Leaderboard, stelle Verbindung her...")
		go g.connectForLeaderboard()
		return
	}

	mode := "solo"
	if g.gameMode == "coop" {
		mode = "coop"
	}

	msg := WebSocketMessage{
		Type: "leaderboard_request",
		Payload: game.LeaderboardRequest{
			Mode: mode,
		},
	}
	g.sendWebSocketMessage(msg)
	log.Printf("🏆 Leaderboard-Request gesendet: Mode=%s", mode)
}

// submitScore sendet den Score ans Leaderboard
func (g *Game) submitScore() {
	if g.scoreSubmitted {
		return
	}

	g.stateMutex.Lock()
	score := 0
	for _, p := range g.gameState.Players {
		if p.Name == g.playerName {
			score = p.Score
			break
		}
	}
	g.stateMutex.Unlock()

	if score == 0 {
		log.Println("⚠️ Score ist 0, überspringe Submission")
		return
	}

	name := g.playerName
	if g.gameMode == "coop" && g.teamName != "" {
		name = g.teamName
	}

	// Verwende Team-Name für Coop-Mode, sonst Player-Name
	displayName := name
	teamName := ""
	playerCodeToUse := g.playerCode

	if g.gameMode == "coop" {
		g.stateMutex.Lock()
		teamName = g.gameState.TeamName
		hostPlayerCode := g.gameState.HostPlayerCode
		g.stateMutex.Unlock()

		if teamName != "" {
			displayName = teamName
		}

		// In Coop: Verwende Host's PlayerCode für Score
		if hostPlayerCode != "" {
			playerCodeToUse = hostPlayerCode
			log.Printf("🔑 Coop-Mode: Verwende Host PlayerCode für Score-Submission")
		}
	}

	msg := WebSocketMessage{
		Type: "score_submit",
		Payload: game.ScoreSubmission{
			PlayerName: displayName,
			PlayerCode: playerCodeToUse,
			Name:       displayName, // Für Kompatibilität
			Score:      score,
			Mode:       g.gameMode,
			TeamName:   teamName, // Team-Name für Coop
		},
	}
	g.sendWebSocketMessage(msg)

	g.scoreSubmitted = true
	log.Printf("📊 Score submitted: %s = %d (PlayerCode: %s, TeamName: %s)", displayName, score, playerCodeToUse, teamName)
}

// Dummy-Funktionen für Kompatibilität mit anderen Teilen des Codes
func (g *Game) sendJoinRequest() {
	// Wird in connectToServer aufgerufen
}

func (g *Game) sendStartRequest() {
	// Warte bis WebSocket verbunden ist
	for i := 0; i < 30; i++ {
		if g.connected && g.wsConn != nil && g.wsConn.connected {
			break
		}
		time.Sleep(100 * time.Millisecond)
	}

	if !g.connected {
		log.Println("❌ Kann START nicht senden - keine Verbindung")
		return
	}

	g.startGame()
}

func (g *Game) publishInput(input game.ClientInput) {
	g.sendInput(input)
}

// sendSetTeamNameInput sendet SET_TEAM_NAME Input über WebSocket
func (g *Game) sendSetTeamNameInput(teamName string) {
	if !g.connected {
		log.Println("❌ Kann Team-Name nicht senden - keine Verbindung")
		return
	}

	myID := g.getMyPlayerID()
	input := game.ClientInput{
		Type:     "SET_TEAM_NAME",
		RoomID:   g.roomID,
		PlayerID: myID,
		TeamName: teamName,
	}

	g.sendInput(input)
	log.Printf("🏷️  SET_TEAM_NAME gesendet: '%s'", teamName)
}
