package sdk

import (
	"context"
	"slices"
	"time"

	"github.com/nbd-wtf/go-nostr"
	"github.com/nbd-wtf/go-nostr/sdk/cache"
)

// this is similar to list.go and inherits code from that.

type GenericSets[I TagItemWithValue] struct {
	PubKey string         `json:"-"`
	Events []*nostr.Event `json:"-"`

	Sets map[string][]I
}

func fetchGenericSets[I TagItemWithValue](
	sys *System,
	ctx context.Context,
	pubkey string,
	actualKind int,
	addressableIndex addressableIndex,
	parseTag func(nostr.Tag) (I, bool),
	cache cache.Cache32[GenericSets[I]],
	skipFetch bool,
) (fl GenericSets[I], fromInternal bool) {
	// we have 24 mutexes, so we can load up to 24 lists at the same time, but if we do the same exact
	// call that will do it only once, the subsequent ones will wait for a result to be cached
	// and then return it from cache -- 13 is an arbitrary index for the pubkey
	lockIdx := (int(pubkey[13]) + int(addressableIndex)) % 24
	genericListMutexes[lockIdx].Lock()

	if valueWasJustCached[lockIdx] {
		// this ensures the cache has had time to commit the values
		// so we don't repeat a fetch immediately after the other
		valueWasJustCached[lockIdx] = false
		time.Sleep(time.Millisecond * 10)
	}

	defer genericListMutexes[lockIdx].Unlock()

	if v, ok := cache.Get(pubkey); ok {
		return v, true
	}

	v := GenericSets[I]{PubKey: pubkey}

	events, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []int{actualKind}, Authors: []string{pubkey}})
	if len(events) != 0 {
		sets := parseSetsFromEvents(events, parseTag)
		v.Events = events
		v.Sets = sets
		cache.SetWithTTL(pubkey, v, time.Hour*6)
		valueWasJustCached[lockIdx] = true
		return v, true
	}

	if !skipFetch {
		thunk := sys.addressableLoaders[addressableIndex].Load(ctx, pubkey)
		events, err := thunk()
		if err == nil {
			sets := parseSetsFromEvents(events, parseTag)
			v.Sets = sets
			for _, evt := range events {
				sys.StoreRelay.Publish(ctx, *evt)
			}
		}
		cache.SetWithTTL(pubkey, v, time.Hour*6)
		valueWasJustCached[lockIdx] = true
	}

	return v, false
}

func parseSetsFromEvents[I TagItemWithValue](
	events []*nostr.Event,
	parseTag func(nostr.Tag) (I, bool),
) map[string][]I {
	sets := make(map[string][]I, len(events))
	for _, evt := range events {
		items := make([]I, 0, len(evt.Tags))
		for _, tag := range evt.Tags {
			item, ok := parseTag(tag)
			if ok {
				// check if this already exists before adding
				if slices.IndexFunc(items, func(i I) bool { return i.Value() == item.Value() }) == -1 {
					items = append(items, item)
				}
			}
		}
		sets[evt.Tags.GetD()] = items
	}
	return sets
}
