package infra

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"net/http"
	"time"

	"nhooyr.io/websocket"
)

// WSHandler retorna un http.HandlerFunc que upgradea la conexion HTTP a WebSocket,
// crea un WSClient, lo registra en el hub y lanza dos goroutines:
//   - readPump: lee mensajes del Conn y los publica al hub.Broadcast
//   - writePump: consume del client.Send y escribe al Conn
//
// El cliente se desregistra del hub cuando alguna de las pumps termina (cliente
// desconectado, error de I/O, o canal cerrado). Asigna un ID hex aleatorio si
// no se sobreescribe externamente.
func WSHandler(hub *WSHub, origins []string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		conn, err := WSUpgrader(w, r, origins)
		if err != nil {
			return
		}

		client := &WSClient{
			Hub:  hub,
			Conn: conn,
			Send: make(chan []byte, 64),
			ID:   randomID(),
		}
		hub.Register <- client

		// writePump
		go wsWritePump(client)
		// readPump (bloqueante en el handler para mantener viva la request)
		wsReadPump(client)
	}
}

// wsReadPump lee mensajes del Conn y los publica al hub.Broadcast.
// Termina si Read retorna error (cliente desconectado o cerrado).
// Al terminar, desregistra el cliente y cierra la conexion.
func wsReadPump(client *WSClient) {
	defer func() {
		// Unregister no bloqueante: si el hub ya esta cerrado, no esperamos
		select {
		case client.Hub.Unregister <- client:
		case <-client.Hub.done:
		}
		_ = client.Conn.Close(websocket.StatusNormalClosure, "")
	}()

	ctx := context.Background()
	for {
		_, data, err := client.Conn.Read(ctx)
		if err != nil {
			return
		}
		// Encolar al hub.Broadcast sin bloquear si esta lleno
		select {
		case client.Hub.Broadcast <- data:
		default:
			// Hub saturado: dropear mensaje del cliente para no bloquear el read
		}
	}
}

// wsWritePump consume del canal Send del cliente y escribe al Conn.
// Termina si el canal se cierra (hub desregistro al cliente) o si Write falla.
func wsWritePump(client *WSClient) {
	for msg := range client.Send {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		err := client.Conn.Write(ctx, websocket.MessageText, msg)
		cancel()
		if err != nil {
			return
		}
	}
}

// randomID genera un identificador hex aleatorio de 16 caracteres (8 bytes).
// No es criptograficamente perfecto para autenticacion — solo identificacion.
func randomID() string {
	b := make([]byte, 8)
	if _, err := rand.Read(b); err != nil {
		return "anon"
	}
	return hex.EncodeToString(b)
}
