package sdk

import (
	"context"

	"github.com/fiatjaf/eventstore"
	"github.com/fiatjaf/eventstore/slicestore"
	"github.com/graph-gophers/dataloader/v7"
	"github.com/nbd-wtf/go-nostr"
	"github.com/nbd-wtf/go-nostr/sdk/cache"
	cache_memory "github.com/nbd-wtf/go-nostr/sdk/cache/memory"
	"github.com/nbd-wtf/go-nostr/sdk/hints"
	memory_hints "github.com/nbd-wtf/go-nostr/sdk/hints/memory"
)

type System struct {
	RelayListCache   cache.Cache32[RelayList]
	FollowListCache  cache.Cache32[FollowList]
	MetadataCache    cache.Cache32[ProfileMetadata]
	Hints            hints.HintsDB
	Pool             *nostr.SimplePool
	RelayListRelays  []string
	FollowListRelays []string
	MetadataRelays   []string
	FallbackRelays   []string
	UserSearchRelays []string
	NoteSearchRelays []string
	Store            eventstore.Store

	StoreRelay nostr.RelayStore

	replaceableLoaders   map[int]*dataloader.Loader[string, *nostr.Event]
	outboxShortTermCache cache.Cache32[[]string]
}

type SystemModifier func(sys *System)

func NewSystem(mods ...SystemModifier) *System {
	sys := &System{
		RelayListCache:   cache_memory.New32[RelayList](1000),
		FollowListCache:  cache_memory.New32[FollowList](1000),
		MetadataCache:    cache_memory.New32[ProfileMetadata](1000),
		RelayListRelays:  []string{"wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"},
		FollowListRelays: []string{"wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"},
		MetadataRelays:   []string{"wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"},
		FallbackRelays: []string{
			"wss://relay.primal.net",
			"wss://relay.damus.io",
			"wss://nostr.wine",
			"wss://nostr.mom",
			"wss://offchain.pub",
			"wss://nos.lol",
			"wss://mostr.pub",
			"wss://relay.nostr.band",
			"wss://nostr21.com",
		},
		UserSearchRelays: []string{
			"wss://nostr.wine",
			"wss://relay.nostr.band",
			"wss://relay.noswhere.com",
		},
		NoteSearchRelays: []string{
			"wss://nostr.wine",
			"wss://relay.nostr.band",
			"wss://relay.noswhere.com",
		},
		Hints: memory_hints.NewHintDB(),

		outboxShortTermCache: cache_memory.New32[[]string](1000),
	}

	sys.Pool = nostr.NewSimplePool(context.Background(),
		nostr.WithEventMiddleware(sys.TrackEventHints),
		nostr.WithPenaltyBox(),
	)

	for _, mod := range mods {
		mod(sys)
	}

	if sys.Store == nil {
		sys.Store = &slicestore.SliceStore{}
		sys.Store.Init()
	}
	sys.StoreRelay = eventstore.RelayWrapper{Store: sys.Store}

	sys.initializeDataloaders()

	return sys
}

func (sys *System) Close() {}

func WithHintsDB(hdb hints.HintsDB) SystemModifier {
	return func(sys *System) {
		sys.Hints = hdb
	}
}

func WithRelayListRelays(list []string) SystemModifier {
	return func(sys *System) {
		sys.RelayListRelays = list
	}
}

func WithMetadataRelays(list []string) SystemModifier {
	return func(sys *System) {
		sys.MetadataRelays = list
	}
}

func WithFollowListRelays(list []string) SystemModifier {
	return func(sys *System) {
		sys.FollowListRelays = list
	}
}

func WithFallbackRelays(list []string) SystemModifier {
	return func(sys *System) {
		sys.FallbackRelays = list
	}
}

func WithUserSearchRelays(list []string) SystemModifier {
	return func(sys *System) {
		sys.UserSearchRelays = list
	}
}

func WithNoteSearchRelays(list []string) SystemModifier {
	return func(sys *System) {
		sys.NoteSearchRelays = list
	}
}

func WithStore(store eventstore.Store) SystemModifier {
	return func(sys *System) {
		sys.Store = store
	}
}

func WithRelayListCache(cache cache.Cache32[RelayList]) SystemModifier {
	return func(sys *System) {
		sys.RelayListCache = cache
	}
}

func WithFollowListCache(cache cache.Cache32[FollowList]) SystemModifier {
	return func(sys *System) {
		sys.FollowListCache = cache
	}
}

func WithMetadataCache(cache cache.Cache32[ProfileMetadata]) SystemModifier {
	return func(sys *System) {
		sys.MetadataCache = cache
	}
}