package main

import (
	"context"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fiatjaf/eventstore"
	"github.com/fiatjaf/eventstore/badger"
	"github.com/fiatjaf/khatru"
	"github.com/fiatjaf/khatru/policies"
	"github.com/joho/godotenv"
	"github.com/nbd-wtf/go-nostr"
)

var (
	version string
)

type Config struct {
	RelayName        string
	RelayPubkey      string
	RelayDescription string
	DBPath           string
	RelayURL         string
	IndexPath        string
	StaticPath       string
	RefreshInterval  int
	MinimumFollowers int
	ArchivalSync     bool
	RelayContact     string
	RelayIcon        string
	MaxAgeDays       int
	ArchiveReactions bool
	IgnoredPubkeys   []string
	MaxTrustNetwork  int
	MaxRelays        int
	MaxOneHopNetwork int
}

var pool *nostr.SimplePool
var wdb nostr.RelayStore
var relays []string
var relaySet = make(map[string]bool) // O(1) lookup
var config Config
var trustNetwork []string
var trustNetworkSet = make(map[string]bool) // O(1) lookup
var seedRelays []string
var booted bool
var oneHopNetwork []string
var oneHopNetworkSet = make(map[string]bool) // O(1) lookup
var trustNetworkMap map[string]bool
var pubkeyFollowerCount = make(map[string]int)
var trustedNotes uint64
var untrustedNotes uint64
var archiveEventSemaphore = make(chan struct{}, 20) // Reduced from 100 to 20

// Performance counters
var (
	totalEvents         uint64
	rejectedEvents      uint64
	archivedEvents      uint64
	profileRefreshCount uint64
	networkRefreshCount uint64
)

// Mutexes for thread safety
var (
	relayMutex        sync.RWMutex
	trustNetworkMutex sync.RWMutex
	oneHopMutex       sync.RWMutex
	followerMutex     sync.RWMutex
)

func main() {
	nostr.InfoLogger = log.New(io.Discard, "", 0)
	booted = false
	green := "\033[32m"
	reset := "\033[0m"

	art := `
888       888      88888888888      8888888b.          888                   
888   o   888          888          888   Y88b         888                   
888  d8b  888          888          888    888         888                   
888 d888b 888  .d88b.  888          888   d88P .d88b.  888  8888b.  888  888 
888d88888b888 d88""88b 888          8888888P" d8P  Y8b 888     "88b 888  888 
88888P Y88888 888  888 888          888 T88b  88888888 888 .d888888 888  888 
8888P   Y8888 Y88..88P 888          888  T88b Y8b.     888 888  888 Y88b 888 
888P     Y888  "Y88P"  888          888   T88b "Y8888  888 "Y888888  "Y88888 
                                                                         888 
                                                                    Y8b d88P 
                                               powered by: khatru     "Y88P"  
	`

	fmt.Println(green + art + reset)
	log.Println("🚀 booting up web of trust relay")
	relay := khatru.NewRelay()
	ctx := context.Background()
	pool = nostr.NewSimplePool(ctx)
	config = LoadConfig()

	relay.Info.Name = config.RelayName
	relay.Info.PubKey = config.RelayPubkey
	relay.Info.Icon = config.RelayIcon
	relay.Info.Contact = config.RelayContact
	relay.Info.Description = config.RelayDescription
	relay.Info.Software = "https://github.com/bitvora/wot-relay"
	relay.Info.Version = version

	appendPubkey(config.RelayPubkey)

	db := getDB()
	if err := db.Init(); err != nil {
		panic(err)
	}
	wdb = eventstore.RelayWrapper{Store: &db}

	relay.RejectEvent = append(relay.RejectEvent,
		policies.RejectEventsWithBase64Media,
		policies.EventIPRateLimiter(5, time.Minute*1, 30),
	)

	relay.RejectFilter = append(relay.RejectFilter,
		policies.NoEmptyFilters,
		policies.NoComplexFilters,
		policies.FilterIPRateLimiter(5, time.Minute*1, 30),
	)

	relay.RejectConnection = append(relay.RejectConnection,
		policies.ConnectionRateLimiter(10, time.Minute*2, 30),
	)

	relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
	relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
	relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
	relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) {
		atomic.AddUint64(&totalEvents, 1)

		// Don't reject events if we haven't booted yet or if trust network is empty
		if !booted {
			return false, ""
		}

		trustNetworkMutex.RLock()
		trusted := trustNetworkMap[event.PubKey]
		hasNetwork := len(trustNetworkMap) > 0
		trustNetworkMutex.RUnlock()

		// If we don't have a trust network yet, allow all events
		if !hasNetwork {
			return false, ""
		}

		if !trusted {
			atomic.AddUint64(&rejectedEvents, 1)
			return true, "not in web of trust"
		}
		if event.Kind == nostr.KindEncryptedDirectMessage {
			atomic.AddUint64(&rejectedEvents, 1)
			return true, "only gift wrapped DMs are allowed"
		}

		return false, ""
	})

	seedRelays = []string{
		"wss://nos.lol",
		"wss://nostr.mom",
		"wss://purplepag.es",
		"wss://purplerelay.com",
		"wss://relay.damus.io",
		"wss://relay.nostr.band",
		"wss://relay.snort.social",
		"wss://relayable.org",
		"wss://relay.primal.net",
		"wss://relay.nostr.bg",
		"wss://no.str.cr",
		"wss://nostr21.com",
		"wss://nostrue.com",
		"wss://relay.siamstr.com",
	}

	go refreshTrustNetwork(ctx, relay)
	go monitorMemoryUsage() // Add memory monitoring
	go monitorPerformance() // Add performance monitoring

	mux := relay.Router()
	static := http.FileServer(http.Dir(config.StaticPath))

	mux.Handle("GET /static/", http.StripPrefix("/static/", static))
	mux.Handle("GET /favicon.ico", http.StripPrefix("/", static))

	// Add debug endpoints
	mux.HandleFunc("GET /debug/stats", debugStatsHandler)
	mux.HandleFunc("GET /debug/goroutines", debugGoroutinesHandler)

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		tmpl := template.Must(template.ParseFiles(os.Getenv("INDEX_PATH")))
		data := struct {
			RelayName        string
			RelayPubkey      string
			RelayDescription string
			RelayURL         string
		}{
			RelayName:        config.RelayName,
			RelayPubkey:      config.RelayPubkey,
			RelayDescription: config.RelayDescription,
			RelayURL:         config.RelayURL,
		}
		err := tmpl.Execute(w, data)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	})

	log.Println("🎉 relay running on port :3334")
	log.Println("🔍 debug endpoints available at:")
	log.Println("   http://localhost:3334/debug/pprof/ (CPU/memory profiling)")
	log.Println("   http://localhost:3334/debug/stats (application stats)")
	log.Println("   http://localhost:3334/debug/goroutines (goroutine info)")
	err := http.ListenAndServe(":3334", relay)
	if err != nil {
		log.Fatal(err)
	}
}

func LoadConfig() Config {
	godotenv.Load(".env")

	if os.Getenv("REFRESH_INTERVAL_HOURS") == "" {
		os.Setenv("REFRESH_INTERVAL_HOURS", "3")
	}

	refreshInterval, _ := strconv.Atoi(os.Getenv("REFRESH_INTERVAL_HOURS"))
	log.Println("🔄 refresh interval set to", refreshInterval, "hours")

	if os.Getenv("MINIMUM_FOLLOWERS") == "" {
		os.Setenv("MINIMUM_FOLLOWERS", "1")
	}

	if os.Getenv("ARCHIVAL_SYNC") == "" {
		os.Setenv("ARCHIVAL_SYNC", "TRUE")
	}

	if os.Getenv("RELAY_ICON") == "" {
		os.Setenv("RELAY_ICON", "https://pfp.nostr.build/56306a93a88d4c657d8a3dfa57b55a4ed65b709eee927b5dafaab4d5330db21f.png")
	}

	if os.Getenv("RELAY_CONTACT") == "" {
		os.Setenv("RELAY_CONTACT", getEnv("RELAY_PUBKEY"))
	}

	if os.Getenv("MAX_AGE_DAYS") == "" {
		os.Setenv("MAX_AGE_DAYS", "0")
	}

	if os.Getenv("ARCHIVE_REACTIONS") == "" {
		os.Setenv("ARCHIVE_REACTIONS", "FALSE")
	}

	if os.Getenv("MAX_TRUST_NETWORK") == "" {
		os.Setenv("MAX_TRUST_NETWORK", "40000")
	}

	if os.Getenv("MAX_RELAYS") == "" {
		os.Setenv("MAX_RELAYS", "1000")
	}

	if os.Getenv("MAX_ONE_HOP_NETWORK") == "" {
		os.Setenv("MAX_ONE_HOP_NETWORK", "50000")
	}

	ignoredPubkeys := []string{}
	if ignoreList := os.Getenv("IGNORE_FOLLOWS_LIST"); ignoreList != "" {
		ignoredPubkeys = splitAndTrim(ignoreList)
	}

	minimumFollowers, _ := strconv.Atoi(os.Getenv("MINIMUM_FOLLOWERS"))
	maxAgeDays, _ := strconv.Atoi(os.Getenv("MAX_AGE_DAYS"))
	maxTrustNetwork, _ := strconv.Atoi(os.Getenv("MAX_TRUST_NETWORK"))
	maxRelays, _ := strconv.Atoi(os.Getenv("MAX_RELAYS"))
	maxOneHopNetwork, _ := strconv.Atoi(os.Getenv("MAX_ONE_HOP_NETWORK"))

	config := Config{
		RelayName:        getEnv("RELAY_NAME"),
		RelayPubkey:      getEnv("RELAY_PUBKEY"),
		RelayDescription: getEnv("RELAY_DESCRIPTION"),
		RelayContact:     getEnv("RELAY_CONTACT"),
		RelayIcon:        getEnv("RELAY_ICON"),
		DBPath:           getEnv("DB_PATH"),
		RelayURL:         getEnv("RELAY_URL"),
		IndexPath:        getEnv("INDEX_PATH"),
		StaticPath:       getEnv("STATIC_PATH"),
		RefreshInterval:  refreshInterval,
		MinimumFollowers: minimumFollowers,
		ArchivalSync:     getEnv("ARCHIVAL_SYNC") == "TRUE",
		MaxAgeDays:       maxAgeDays,
		ArchiveReactions: getEnv("ARCHIVE_REACTIONS") == "TRUE",
		IgnoredPubkeys:   ignoredPubkeys,
		MaxTrustNetwork:  maxTrustNetwork,
		MaxRelays:        maxRelays,
		MaxOneHopNetwork: maxOneHopNetwork,
	}

	return config
}

func getEnv(key string) string {
	value, exists := os.LookupEnv(key)
	if !exists {
		log.Fatalf("Environment variable %s not set", key)
	}
	return value
}

func updateTrustNetworkFilter() {
	// Build new trust network in temporary variables
	newTrustNetworkMap := make(map[string]bool)
	var newTrustNetwork []string
	newTrustNetworkSet := make(map[string]bool)

	log.Println("🌐 building new trust network map")

	followerMutex.RLock()
	for pubkey, count := range pubkeyFollowerCount {
		if count >= config.MinimumFollowers {
			newTrustNetworkMap[pubkey] = true
			if !newTrustNetworkSet[pubkey] && len(pubkey) == 64 && len(newTrustNetwork) < config.MaxTrustNetwork {
				newTrustNetwork = append(newTrustNetwork, pubkey)
				newTrustNetworkSet[pubkey] = true
			}
		}
	}
	followerMutex.RUnlock()

	// Now atomically replace the active trust network
	trustNetworkMutex.Lock()
	trustNetworkMap = newTrustNetworkMap
	trustNetwork = newTrustNetwork
	trustNetworkSet = newTrustNetworkSet
	trustNetworkMutex.Unlock()

	log.Println("🌐 trust network map updated with", len(newTrustNetwork), "keys")

	// Cleanup follower count map periodically to prevent unbounded growth
	followerMutex.Lock()
	if len(pubkeyFollowerCount) > config.MaxOneHopNetwork*2 {
		log.Println("🧹 cleaning follower count map")
		newFollowerCount := make(map[string]int)
		for pubkey, count := range pubkeyFollowerCount {
			if count >= config.MinimumFollowers || newTrustNetworkMap[pubkey] {
				newFollowerCount[pubkey] = count
			}
		}
		oldCount := len(pubkeyFollowerCount)
		pubkeyFollowerCount = newFollowerCount
		log.Printf("🧹 cleaned follower count map: %d -> %d entries", oldCount, len(newFollowerCount))
	}
	followerMutex.Unlock()
}

func refreshProfiles(ctx context.Context) {
	atomic.AddUint64(&profileRefreshCount, 1)
	start := time.Now()

	// Get a snapshot of current trust network to avoid holding locks during network operations
	trustNetworkMutex.RLock()
	currentTrustNetwork := make([]string, len(trustNetwork))
	copy(currentTrustNetwork, trustNetwork)
	trustNetworkMutex.RUnlock()

	for i := 0; i < len(currentTrustNetwork); i += 200 {
		timeout, cancel := context.WithTimeout(ctx, 4*time.Second)

		end := i + 200
		if end > len(currentTrustNetwork) {
			end = len(currentTrustNetwork)
		}

		filters := []nostr.Filter{{
			Authors: currentTrustNetwork[i:end],
			Kinds:   []int{nostr.KindProfileMetadata},
		}}

		for ev := range pool.SubManyEose(timeout, seedRelays, filters) {
			wdb.Publish(ctx, *ev.Event)
		}

		cancel() // Cancel after each iteration
	}
	duration := time.Since(start)
	log.Printf("👤 profiles refreshed: %d profiles in %v", len(currentTrustNetwork), duration)
}

func refreshTrustNetwork(ctx context.Context, relay *khatru.Relay) {
	runTrustNetworkRefresh := func() {
		atomic.AddUint64(&networkRefreshCount, 1)
		start := time.Now()

		// Build new networks in temporary variables to avoid disrupting the active network
		var newOneHopNetwork []string
		newOneHopNetworkSet := make(map[string]bool)
		newPubkeyFollowerCount := make(map[string]int)

		// Copy existing follower counts to preserve data
		followerMutex.RLock()
		for k, v := range pubkeyFollowerCount {
			newPubkeyFollowerCount[k] = v
		}
		followerMutex.RUnlock()

		timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
		defer cancel()

		filters := []nostr.Filter{{
			Authors: []string{config.RelayPubkey},
			Kinds:   []int{nostr.KindFollowList},
		}}

		log.Println("🔍 fetching owner's follows")
		eventCount := 0
		for ev := range pool.SubManyEose(timeoutCtx, seedRelays, filters) {
			eventCount++
			for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
				pubkey := contact[1]
				if isIgnored(pubkey, config.IgnoredPubkeys) {
					fmt.Println("ignoring follows from pubkey: ", pubkey)
					continue
				}
				newPubkeyFollowerCount[contact[1]]++

				// Add to new one-hop network
				if !newOneHopNetworkSet[contact[1]] && len(contact[1]) == 64 && len(newOneHopNetwork) < config.MaxOneHopNetwork {
					newOneHopNetwork = append(newOneHopNetwork, contact[1])
					newOneHopNetworkSet[contact[1]] = true
				}
			}
		}
		log.Printf("🔍 processed %d follow list events", eventCount)

		log.Println("🌐 building web of trust graph")
		totalProcessed := 0
		for i := 0; i < len(newOneHopNetwork); i += 100 {
			timeout, cancel := context.WithTimeout(ctx, 4*time.Second)

			end := i + 100
			if end > len(newOneHopNetwork) {
				end = len(newOneHopNetwork)
			}

			filters = []nostr.Filter{{
				Authors: newOneHopNetwork[i:end],
				Kinds:   []int{nostr.KindFollowList, nostr.KindRelayListMetadata, nostr.KindProfileMetadata},
			}}

			batchCount := 0
			for ev := range pool.SubManyEose(timeout, seedRelays, filters) {
				batchCount++
				totalProcessed++
				for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
					if len(contact) > 1 {
						newPubkeyFollowerCount[contact[1]]++
					}
				}

				for _, relay := range ev.Event.Tags.GetAll([]string{"r"}) {
					appendRelay(relay[1])
				}

				if ev.Event.Kind == nostr.KindProfileMetadata {
					wdb.Publish(ctx, *ev.Event)
				}
			}
			cancel() // Cancel after each iteration

			if i%500 == 0 { // Log progress every 5 batches
				log.Printf("🌐 processed batch %d-%d (%d events in this batch)", i, end, batchCount)
			}
		}

		// Now atomically replace the active data structures
		oneHopMutex.Lock()
		oneHopNetwork = newOneHopNetwork
		oneHopNetworkSet = newOneHopNetworkSet
		oneHopMutex.Unlock()

		followerMutex.Lock()
		pubkeyFollowerCount = newPubkeyFollowerCount
		followerMutex.Unlock()

		duration := time.Since(start)
		log.Printf("🫂 total network size: %d (processed %d events in %v)", len(newPubkeyFollowerCount), totalProcessed, duration)
		relayMutex.RLock()
		log.Println("🔗 relays discovered:", len(relays))
		relayMutex.RUnlock()
	}

	ticker := time.NewTicker(time.Duration(config.RefreshInterval) * time.Hour)
	defer ticker.Stop()

	// Run initial refresh
	log.Println("🚀 performing initial trust network build...")
	runTrustNetworkRefresh()
	updateTrustNetworkFilter()

	// Mark as booted after initial trust network is built
	booted = true
	log.Println("✅ trust network initialized, relay is now active")

	deleteOldNotes(relay)
	archiveTrustedNotes(ctx, relay)

	// Then run on timer
	for {
		select {
		case <-ticker.C:
			log.Println("🔄 refreshing trust network in background...")
			runTrustNetworkRefresh()
			updateTrustNetworkFilter()
			deleteOldNotes(relay)
			archiveTrustedNotes(ctx, relay)
			log.Println("✅ trust network refresh completed")
		case <-ctx.Done():
			return
		}
	}
}

func appendRelay(relay string) {
	relayMutex.Lock()
	defer relayMutex.Unlock()

	if len(relays) >= config.MaxRelays {
		return // Prevent unbounded growth
	}

	if relaySet[relay] {
		return // Already exists
	}

	relays = append(relays, relay)
	relaySet[relay] = true
}

func appendPubkey(pubkey string) {
	trustNetworkMutex.Lock()
	defer trustNetworkMutex.Unlock()

	if len(trustNetwork) >= config.MaxTrustNetwork {
		return // Prevent unbounded growth
	}

	if trustNetworkSet[pubkey] {
		return // Already exists
	}

	if len(pubkey) != 64 {
		return
	}

	trustNetwork = append(trustNetwork, pubkey)
	trustNetworkSet[pubkey] = true
}

func archiveTrustedNotes(ctx context.Context, relay *khatru.Relay) {
	timeout, cancel := context.WithTimeout(ctx, time.Duration(config.RefreshInterval)*time.Hour)
	defer cancel()

	done := make(chan struct{})

	go func() {
		defer close(done)
		if config.ArchivalSync {
			go refreshProfiles(ctx)

			var filters []nostr.Filter
			since := nostr.Now()
			if config.ArchiveReactions {
				filters = []nostr.Filter{{
					Kinds: []int{
						nostr.KindArticle,
						nostr.KindDeletion,
						nostr.KindFollowList,
						nostr.KindEncryptedDirectMessage,
						nostr.KindMuteList,
						nostr.KindReaction,
						nostr.KindRelayListMetadata,
						nostr.KindRepost,
						nostr.KindZapRequest,
						nostr.KindZap,
						nostr.KindTextNote,
					},
					Since: &since,
				}}
			} else {
				filters = []nostr.Filter{{
					Kinds: []int{
						nostr.KindArticle,
						nostr.KindDeletion,
						nostr.KindFollowList,
						nostr.KindEncryptedDirectMessage,
						nostr.KindMuteList,
						nostr.KindRelayListMetadata,
						nostr.KindRepost,
						nostr.KindZapRequest,
						nostr.KindZap,
						nostr.KindTextNote,
					},
					Since: &since,
				}}
			}

			log.Println("📦 archiving trusted notes...")

			eventCount := 0
			for ev := range pool.SubMany(timeout, seedRelays, filters) {
				eventCount++

				// Check GC pressure every 1000 events
				if eventCount%1000 == 0 {
					var m runtime.MemStats
					runtime.ReadMemStats(&m)
					if m.NumGC > 0 && eventCount > 1000 {
						// If we're doing more than 2 GCs per 1000 events, slow down
						gcRate := float64(m.NumGC) / float64(eventCount/1000)
						if gcRate > 2.0 {
							log.Printf("⚠️ High GC pressure (%.1f GC/1000 events), slowing archive process", gcRate)
							time.Sleep(100 * time.Millisecond) // Brief pause
						}
					}
				}

				// Use semaphore to limit concurrent goroutines
				select {
				case archiveEventSemaphore <- struct{}{}:
					go func(event nostr.Event) {
						defer func() { <-archiveEventSemaphore }()
						archiveEvent(ctx, relay, event)
					}(*ev.Event)
				case <-timeout.Done():
					log.Printf("📦 archive timeout reached, processed %d events", eventCount)
					return
				default:
					// If semaphore is full, process synchronously to avoid buildup
					archiveEvent(ctx, relay, *ev.Event)
				}
			}

			log.Printf("📦 archived %d trusted notes and discarded %d untrusted notes (processed %d total events)",
				atomic.LoadUint64(&trustedNotes), atomic.LoadUint64(&untrustedNotes), eventCount)
		} else {
			log.Println("🔄 web of trust will refresh in", config.RefreshInterval, "hours")
			select {
			case <-timeout.Done():
			}
		}
	}()

	select {
	case <-timeout.Done():
		log.Println("restarting process")
	case <-done:
		log.Println("📦 archiving process completed")
	}
}

func archiveEvent(ctx context.Context, relay *khatru.Relay, ev nostr.Event) {
	trustNetworkMutex.RLock()
	trusted := trustNetworkMap[ev.PubKey]
	trustNetworkMutex.RUnlock()

	if trusted {
		wdb.Publish(ctx, ev)
		relay.BroadcastEvent(&ev)
		atomic.AddUint64(&trustedNotes, 1)
		atomic.AddUint64(&archivedEvents, 1)
	} else {
		atomic.AddUint64(&untrustedNotes, 1)
	}
}

func deleteOldNotes(relay *khatru.Relay) error {
	ctx := context.TODO()

	if config.MaxAgeDays <= 0 {
		log.Printf("MAX_AGE_DAYS disabled")
		return nil
	}

	maxAgeSecs := nostr.Timestamp(config.MaxAgeDays * 86400)
	oldAge := nostr.Now() - maxAgeSecs
	if oldAge <= 0 {
		log.Printf("MAX_AGE_DAYS too large")
		return nil
	}

	filter := nostr.Filter{
		Until: &oldAge,
		Kinds: []int{
			nostr.KindArticle,
			nostr.KindDeletion,
			nostr.KindFollowList,
			nostr.KindEncryptedDirectMessage,
			nostr.KindMuteList,
			nostr.KindReaction,
			nostr.KindRelayListMetadata,
			nostr.KindRepost,
			nostr.KindZapRequest,
			nostr.KindZap,
			nostr.KindTextNote,
		},
		Limit: 1000, // Process in batches to avoid memory issues
	}

	ch, err := relay.QueryEvents[0](ctx, filter)
	if err != nil {
		log.Printf("query error %s", err)
		return err
	}

	// Process events in batches to avoid memory issues
	batchSize := 100
	events := make([]*nostr.Event, 0, batchSize)
	count := 0

	for evt := range ch {
		events = append(events, evt)
		count++

		if len(events) >= batchSize {
			// Delete this batch
			for num_evt, del_evt := range events {
				for _, del := range relay.DeleteEvent {
					if err := del(ctx, del_evt); err != nil {
						log.Printf("error deleting note %d of batch. event id: %s", num_evt, del_evt.ID)
						return err
					}
				}
			}
			events = events[:0] // Reset slice but keep capacity
		}
	}

	// Delete remaining events
	if len(events) > 0 {
		for num_evt, del_evt := range events {
			for _, del := range relay.DeleteEvent {
				if err := del(ctx, del_evt); err != nil {
					log.Printf("error deleting note %d of final batch. event id: %s", num_evt, del_evt.ID)
					return err
				}
			}
		}
	}

	if count == 0 {
		log.Println("0 old notes found")
	} else {
		log.Printf("%d old (until %d) notes deleted", count, oldAge)
	}

	return nil
}

func getDB() badger.BadgerBackend {
	return badger.BadgerBackend{
		Path: getEnv("DB_PATH"),
	}
}

func splitAndTrim(input string) []string {
	items := strings.Split(input, ",")
	for i, item := range items {
		items[i] = strings.TrimSpace(item)
	}
	return items
}

func isIgnored(pubkey string, ignoredPubkeys []string) bool {
	for _, ignored := range ignoredPubkeys {
		if pubkey == ignored {
			return true
		}
	}
	return false
}

// Add memory monitoring
func monitorMemoryUsage() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			var m runtime.MemStats
			runtime.ReadMemStats(&m)

			relayMutex.RLock()
			relayCount := len(relays)
			relayMutex.RUnlock()

			trustNetworkMutex.RLock()
			trustNetworkCount := len(trustNetwork)
			trustNetworkMutex.RUnlock()

			oneHopMutex.RLock()
			oneHopCount := len(oneHopNetwork)
			oneHopMutex.RUnlock()

			followerMutex.RLock()
			followerCount := len(pubkeyFollowerCount)
			followerMutex.RUnlock()

			log.Printf("📊 Memory: Alloc=%d KB, Sys=%d KB, NumGC=%d",
				m.Alloc/1024, m.Sys/1024, m.NumGC)
			log.Printf("📊 Data structures: Relays=%d, TrustNetwork=%d, OneHop=%d, Followers=%d",
				relayCount, trustNetworkCount, oneHopCount, followerCount)
		}
	}
}

// Add performance monitoring
func monitorPerformance() {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	var lastGC uint32
	var lastEvents, lastRejected, lastArchived uint64

	for {
		select {
		case <-ticker.C:
			var m runtime.MemStats
			runtime.ReadMemStats(&m)

			currentEvents := atomic.LoadUint64(&totalEvents)
			currentRejected := atomic.LoadUint64(&rejectedEvents)
			currentArchived := atomic.LoadUint64(&archivedEvents)

			eventsPerMin := currentEvents - lastEvents
			rejectedPerMin := currentRejected - lastRejected
			archivedPerMin := currentArchived - lastArchived
			gcPerMin := m.NumGC - lastGC

			numGoroutines := runtime.NumGoroutine()

			log.Printf("⚡ Performance: Events/min=%d, Rejected/min=%d, Archived/min=%d, GC/min=%d, Goroutines=%d",
				eventsPerMin, rejectedPerMin, archivedPerMin, gcPerMin, numGoroutines)

			if gcPerMin > 60 {
				log.Printf("⚠️  HIGH GC ACTIVITY: %d garbage collections in last minute!", gcPerMin)
			}

			if numGoroutines > 1000 {
				log.Printf("⚠️  HIGH GOROUTINE COUNT: %d goroutines active!", numGoroutines)
			}

			lastGC = m.NumGC
			lastEvents = currentEvents
			lastRejected = currentRejected
			lastArchived = currentArchived
		}
	}
}

// Debug handlers
func debugStatsHandler(w http.ResponseWriter, r *http.Request) {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)

	stats := fmt.Sprintf(`Debug Statistics:

Memory:
  Allocated: %d KB
  System: %d KB
  Total Allocations: %d
  GC Cycles: %d
  Goroutines: %d

Events:
  Total Events: %d
  Rejected Events: %d
  Archived Events: %d
  Trusted Notes: %d
  Untrusted Notes: %d

Refreshes:
  Profile Refreshes: %d
  Network Refreshes: %d

Data Structures:
  Relays: %d
  Trust Network: %d
  One Hop Network: %d
  Follower Count Map: %d
`,
		m.Alloc/1024,
		m.Sys/1024,
		m.Mallocs,
		m.NumGC,
		runtime.NumGoroutine(),
		atomic.LoadUint64(&totalEvents),
		atomic.LoadUint64(&rejectedEvents),
		atomic.LoadUint64(&archivedEvents),
		atomic.LoadUint64(&trustedNotes),
		atomic.LoadUint64(&untrustedNotes),
		atomic.LoadUint64(&profileRefreshCount),
		atomic.LoadUint64(&networkRefreshCount),
		len(relays),
		len(trustNetwork),
		len(oneHopNetwork),
		len(pubkeyFollowerCount),
	)

	w.Header().Set("Content-Type", "text/plain")
	w.Write([]byte(stats))
}

func debugGoroutinesHandler(w http.ResponseWriter, r *http.Request) {
	buf := make([]byte, 1<<20) // 1MB buffer
	stackSize := runtime.Stack(buf, true)

	w.Header().Set("Content-Type", "text/plain")
	w.Write(buf[:stackSize])
}
