package hub

import (
	"encoding/json"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

// WSMessage is sent to browsers (matches frontend ServerStatus shape).
type WSMessage struct {
	Type string                       `json:"type"`
	Data map[string]json.RawMessage `json:"data"`
}

type Hub struct {
	mu sync.RWMutex
	// serverId -> latest metrics JSON object (not wrapped)
	metrics map[string]json.RawMessage

	subsMu sync.Mutex
	// subscribers for broadcast
	conns map[*websocket.Conn]struct{}
}

func New() *Hub {
	return &Hub{
		metrics: make(map[string]json.RawMessage),
		conns:   make(map[*websocket.Conn]struct{}),
	}
}

func (h *Hub) SetMetrics(serverID string, metricsJSON []byte) {
	h.mu.Lock()
	if len(metricsJSON) == 0 {
		delete(h.metrics, serverID)
	} else {
		h.metrics[serverID] = json.RawMessage(append([]byte(nil), metricsJSON...))
	}
	h.mu.Unlock()
}

func (h *Hub) snapshotData() map[string]json.RawMessage {
	h.mu.RLock()
	defer h.mu.RUnlock()
	out := make(map[string]json.RawMessage, len(h.metrics))
	now := time.Now().UnixMilli()
	for id, raw := range h.metrics {
		// Wrap as ServerStatus for frontend compatibility
		wrap := map[string]any{
			"serverId":   id,
			"online":     true,
			"metrics":    json.RawMessage(raw),
			"lastUpdate": now,
		}
		b, err := json.Marshal(wrap)
		if err != nil {
			continue
		}
		out[id] = b
	}
	return out
}

func (h *Hub) BuildSnapshotMessage() ([]byte, error) {
	msg := WSMessage{
		Type: "snapshot",
		Data: h.snapshotData(),
	}
	return json.Marshal(msg)
}

// AddSubscriber registers for broadcast fan-out (call after sending the initial snapshot).
func (h *Hub) AddSubscriber(c *websocket.Conn) {
	h.subsMu.Lock()
	h.conns[c] = struct{}{}
	h.subsMu.Unlock()
}

func (h *Hub) Unregister(c *websocket.Conn) {
	h.subsMu.Lock()
	delete(h.conns, c)
	h.subsMu.Unlock()
}

// BroadcastSnapshot pushes full snapshot to all subscribers.
func (h *Hub) BroadcastSnapshot() {
	body, err := h.BuildSnapshotMessage()
	if err != nil {
		return
	}
	h.subsMu.Lock()
	defer h.subsMu.Unlock()
	for c := range h.conns {
		_ = c.SetWriteDeadline(time.Now().Add(10 * time.Second))
		if err := c.WriteMessage(websocket.TextMessage, body); err != nil {
			_ = c.Close()
			delete(h.conns, c)
		}
	}
}

// NotifyMetricsUpdate should be called after SetMetrics to push to browsers.
func (h *Hub) NotifyMetricsUpdate(serverID string, metricsJSON []byte) {
	h.SetMetrics(serverID, metricsJSON)
	h.BroadcastSnapshot()
}
