package server

import (
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

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

var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}

type Gateway struct {
	NC    *nats.Conn
	World *game.World
	// Lokale Referenz auf Räume, die DIESER Server verwaltet
	// In einer echten Microservice Welt wäre das separat,
	// aber hier hostet der Gateway auch Räume.
	LocalRooms map[string]*Room
}

func NewGateway(nc *nats.Conn, w *game.World) *Gateway {
	return &Gateway{
		NC:         nc,
		World:      w,
		LocalRooms: make(map[string]*Room),
	}
}

func (gw *Gateway) HandleWS(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	defer conn.Close()

	// 1. HANDSHAKE (Login warten)
	// Der Client muss als allererstes JSON senden: {action: "CREATE"|"JOIN", name: "Hans"}
	var login game.LoginPayload
	_, msg, err := conn.ReadMessage()
	if err != nil {
		return
	}

	if err := json.Unmarshal(msg, &login); err != nil {
		log.Println("Ungültiger Login:", err)
		return
	}

	// IDs generieren
	playerID := fmt.Sprintf("p_%d", time.Now().UnixNano())
	roomID := login.RoomID

	// 2. RAUM LOGIK
	if login.Action == "CREATE" {
		// Raum ID generieren (4 Zeichen Random)
		roomID = GenerateRoomCode()

		// Neuen Raum starten (auf diesem Server)
		newRoom := NewRoom(roomID, gw.NC, gw.World)
		gw.LocalRooms[roomID] = newRoom
		go newRoom.RunLoop()

		// Spieler lokal hinzufügen (Hack für Demo, sauberer wäre via NATS Event)
		newRoom.AddPlayer(playerID, login.Name)

	} else if login.Action == "JOIN" {
		// Wir müssen dem Raum (egal wo er läuft) sagen: Hier ist ein Neuer!
		// Da wir hier keine verteilte DB haben, tricksen wir:
		// Wir gehen davon aus, dass wir den Raum "finden" müssen.
		// Für dieses Tutorial: Wir prüfen ob er lokal ist.
		// Wenn er auf einem anderen Server wäre, bräuchten wir ein "PlayerJoin" Subject.

		if room, ok := gw.LocalRooms[roomID]; ok {
			room.AddPlayer(playerID, login.Name)
		} else {
			// Falls Raum nicht lokal: Senden wir ein "JOIN REQUEST" über NATS?
			// Für jetzt: Wir lassen es simpel. Wenn Raum nicht auf diesem Server -> Error.
			// (Für echtes Scaling bräuchten wir Redis oder NATS Request/Reply zur Raumsuche)
			log.Println("Raum nicht gefunden (oder auf anderem Node):", roomID)
			// Optional: Error an Client senden
			return
		}
	}

	log.Printf("Player %s (%s) joined Room %s", playerID, login.Name, roomID)

	// 3. PROXY LOOP
	// A) NATS -> WebSocket (State Updates empfangen)
	sub, _ := gw.NC.Subscribe(fmt.Sprintf("game.room.%s.state", roomID), func(m *nats.Msg) {
		conn.WriteMessage(websocket.TextMessage, m.Data)
	})
	defer sub.Unsubscribe()

	// B) WebSocket -> NATS (Input senden)
	for {
		_, data, err := conn.ReadMessage()
		if err != nil {
			break
		}

		// Wir parsen kurz, um den Typ zu prüfen, oder leiten blind weiter?
		// Besser: Wir wrappen es in ClientInput struct
		var raw map[string]interface{}
		json.Unmarshal(data, &raw)

		inputType, _ := raw["type"].(string)

		input := game.ClientInput{
			Type:     inputType,
			RoomID:   roomID,
			PlayerID: playerID,
		}

		bytes, _ := json.Marshal(input)
		gw.NC.Publish(fmt.Sprintf("game.room.%s.input", roomID), bytes)
	}

	// Cleanup beim Disconnect
	if room, ok := gw.LocalRooms[roomID]; ok {
		room.RemovePlayer(playerID)
		// Wenn leer -> Raum löschen?
	}
}

func GenerateRoomCode() string {
	chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, 4)
	for i := range b {
		b[i] = chars[rand.Intn(len(chars))]
	}
	return string(b)
}
