// client2server - Server-Sent Events broadcaster
//
// Used by the dashboard for real-time event feed. Clients connect to
// /api/events/stream and receive a newline-delimited stream of JSON
// envelopes.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
	"time"
)

type sseClient struct {
	channel  chan []byte
	filter   sseFilter
	lastPing time.Time
}

type sseFilter struct {
	RouterID  string
	EventType string
}

type sseHub struct {
	mu      sync.RWMutex
	clients map[*sseClient]struct{}
}

var hub = &sseHub{
	clients: make(map[*sseClient]struct{}),
}

func (h *sseHub) add(c *sseClient) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.clients[c] = struct{}{}
}

func (h *sseHub) remove(c *sseClient) {
	h.mu.Lock()
	defer h.mu.Unlock()
	delete(h.clients, c)
	close(c.channel)
}

func (h *sseHub) broadcast(eventType string, payload any) {
	env := map[string]any{
		"type":      eventType,
		"payload":   payload,
		"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
	}
	data, err := json.Marshal(env)
	if err != nil {
		return
	}
	h.mu.RLock()
	defer h.mu.RUnlock()
	for c := range h.clients {
		if c.filter.RouterID != "" {
			if rid, ok := payload.(map[string]any)["router_id"].(string); ok && rid != c.filter.RouterID {
				continue
			}
		}
		if c.filter.EventType != "" && c.filter.EventType != eventType {
			continue
		}
		select {
		case c.channel <- data:
		default:
			// drop if client too slow
		}
	}
}

func handleSSEStream(w http.ResponseWriter, r *http.Request) {
	// Auth via Bearer JWT or legacy token
	token := r.Header.Get("Authorization")
	if t := r.URL.Query().Get("token"); t != "" {
		token = "Bearer " + t
	}
	token = trimBearer(token)
	if token == "" {
		http.Error(w, "missing token", http.StatusUnauthorized)
		return
	}
	// Accept either a JWT or the legacy shared TOKEN
	if _, err := ParseJWT(token); err != nil && token != cfg.Token {
		http.Error(w, "invalid token", http.StatusUnauthorized)
		return
	}

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "streaming not supported", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("X-Accel-Buffering", "no")
	w.WriteHeader(http.StatusOK)
	flusher.Flush()

	client := &sseClient{
		channel:  make(chan []byte, 64),
		filter:   sseFilter{RouterID: r.URL.Query().Get("router_id"), EventType: r.URL.Query().Get("event_type")},
		lastPing: time.Now(),
	}
	hub.add(client)
	defer hub.remove(client)

	// Initial comment so curl shows connection is live
	_, _ = fmt.Fprintf(w, ": connected\n\n")
	flusher.Flush()

	pingTicker := time.NewTicker(15 * time.Second)
	defer pingTicker.Stop()

	for {
		select {
		case <-r.Context().Done():
			return
		case data := <-client.channel:
			if _, err := fmt.Fprintf(w, "data: %s\n\n", data); err != nil {
				return
			}
			flusher.Flush()
		case <-pingTicker.C:
			if _, err := fmt.Fprintf(w, ": ping\n\n"); err != nil {
				return
			}
			flusher.Flush()
		}
	}
}

func trimBearer(s string) string {
	if len(s) > 7 && s[:7] == "Bearer " {
		return s[7:]
	}
	return s
}
