package services

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/lib/pq"
	"github.com/tapper/backend/database"
)

type SSEClient struct {
	MatchID string
	Ch      chan string
}

var (
	sseClients    = make(map[string]map[*SSEClient]bool)
	sseMu         sync.RWMutex
	globalClients = make(map[*SSEClient]bool)
	globalMu      sync.RWMutex
)

func RegisterSSEClient(client *SSEClient) {
	sseMu.Lock()
	defer sseMu.Unlock()
	if sseClients[client.MatchID] == nil {
		sseClients[client.MatchID] = make(map[*SSEClient]bool)
	}
	sseClients[client.MatchID][client] = true
}

func UnregisterSSEClient(client *SSEClient) {
	sseMu.Lock()
	defer sseMu.Unlock()
	if clients, ok := sseClients[client.MatchID]; ok {
		delete(clients, client)
		if len(clients) == 0 {
			delete(sseClients, client.MatchID)
		}
	}
}

func RegisterGlobalSSEClient(client *SSEClient) {
	globalMu.Lock()
	defer globalMu.Unlock()
	globalClients[client] = true
}

func UnregisterGlobalSSEClient(client *SSEClient) {
	globalMu.Lock()
	defer globalMu.Unlock()
	delete(globalClients, client)
}

func BroadcastToMatch(matchID string, eventType string, data interface{}) {
	payload, err := json.Marshal(data)
	if err != nil {
		log.Printf("[events] Failed to marshal event: %v", err)
		return
	}

	msg := fmt.Sprintf("event: %s\ndata: %s\n\n", eventType, string(payload))

	sseMu.RLock()
	clients := sseClients[matchID]
	sseMu.RUnlock()

	for client := range clients {
		select {
		case client.Ch <- msg:
		default:
		}
	}

	globalMu.RLock()
	for client := range globalClients {
		select {
		case client.Ch <- msg:
		default:
		}
	}
	globalMu.RUnlock()
}

func PublishEvent(channel string, data interface{}) {
	payload, err := json.Marshal(data)
	if err != nil {
		log.Printf("[events] Failed to marshal NOTIFY payload: %v", err)
		return
	}

	sqlDB, err := database.DB.DB()
	if err != nil {
		log.Printf("[events] Failed to get sql.DB: %v", err)
		return
	}

	_, err = sqlDB.Exec(fmt.Sprintf("SELECT pg_notify('%s', $1)", channel), string(payload))
	if err != nil {
		log.Printf("[events] Failed to NOTIFY: %v", err)
	}
}

func StartEventListener(ctx context.Context, databaseURL string) {
	log.Println("[events] Starting PostgreSQL LISTEN/NOTIFY listener")

	for {
		select {
		case <-ctx.Done():
			return
		default:
		}

		if err := listenLoop(ctx, databaseURL); err != nil {
			log.Printf("[events] Listener error: %v, reconnecting in 5s", err)
			time.Sleep(5 * time.Second)
		}
	}
}

func listenLoop(ctx context.Context, databaseURL string) error {
	listener := pq.NewListener(databaseURL, 10*time.Second, time.Minute, func(ev pq.ListenerEventType, err error) {
		if err != nil {
			log.Printf("[events] Listener event error: %v", err)
		}
	})
	defer listener.Close()

	if err := listener.Listen("match_events"); err != nil {
		return fmt.Errorf("LISTEN: %w", err)
	}
	log.Println("[events] Listening on channel: match_events")

	for {
		select {
		case <-ctx.Done():
			return nil
		case notification := <-listener.Notify:
			if notification == nil {
				continue
			}
			handleNotification(notification)
		case <-time.After(90 * time.Second):
			if err := listener.Ping(); err != nil {
				return fmt.Errorf("ping: %w", err)
			}
		}
	}
}

func handleNotification(n *pq.Notification) {
	var payload map[string]interface{}
	if err := json.Unmarshal([]byte(n.Extra), &payload); err != nil {
		log.Printf("[events] Failed to parse notification: %v", err)
		return
	}

	matchID, _ := payload["match_id"].(string)
	eventType, _ := payload["event"].(string)
	if matchID == "" || eventType == "" {
		return
	}

	BroadcastToMatch(matchID, eventType, payload)
}
