package bluge

import (
	"context"
	"iter"
	"strconv"

	"fiatjaf.com/nostr"
	"github.com/blugelabs/bluge"
	"github.com/blugelabs/bluge/search"
)

func (b *BlugeBackend) QueryEvents(filter nostr.Filter, maxLimit int) iter.Seq[nostr.Event] {
	return func(yield func(nostr.Event) bool) {
		limit := maxLimit
		if filter.LimitZero {
			return
		} else if filter.Limit < limit {
			limit = filter.Limit
		}

		if len(filter.Search) < 2 {
			return
		}

		reader, err := b.writer.Reader()
		if err != nil {
			return
		}

		searchQ := bluge.NewMatchQuery(filter.Search)
		searchQ.SetField(contentField)
		var q bluge.Query = searchQ

		complicatedQuery := bluge.NewBooleanQuery().AddMust(searchQ)

		if len(filter.Kinds) > 0 {
			eitherKind := bluge.NewBooleanQuery()
			eitherKind.SetMinShould(1)
			for _, kind := range filter.Kinds {
				kindQ := bluge.NewTermQuery(strconv.Itoa(int(kind)))
				kindQ.SetField(kindField)
				eitherKind.AddShould(kindQ)
			}
			complicatedQuery.AddMust(eitherKind)
			q = complicatedQuery
		}

		if len(filter.Authors) > 0 {
			eitherPubkey := bluge.NewBooleanQuery()
			eitherPubkey.SetMinShould(1)
			for _, pubkey := range filter.Authors {
				if len(pubkey) != 64 {
					continue
				}
				pubkeyQ := bluge.NewTermQuery(pubkey.Hex()[56:])
				pubkeyQ.SetField(pubkeyField)
				eitherPubkey.AddShould(pubkeyQ)
			}
			complicatedQuery.AddMust(eitherPubkey)
			q = complicatedQuery
		}

		if filter.Since != 0 || filter.Until != 0 {
			min := 0.0
			if filter.Since != 0 {
				min = float64(filter.Since)
			}
			max := float64(nostr.Now())
			if filter.Until != 0 {
				max = float64(filter.Until)
			}
			dateRangeQ := bluge.NewNumericRangeInclusiveQuery(min, max, true, true)
			dateRangeQ.SetField(createdAtField)
			complicatedQuery.AddMust(dateRangeQ)
			q = complicatedQuery
		}

		req := bluge.NewTopNSearch(limit, q)

		dmi, err := reader.Search(context.Background(), req)
		if err != nil {
			reader.Close()
			return
		}

		defer reader.Close()

		var next *search.DocumentMatch
		for next, err = dmi.Next(); next != nil; next, err = dmi.Next() {
			next.VisitStoredFields(func(field string, value []byte) bool {
				id, err := nostr.IDFromHex(string(value))
				if err == nil {
					for evt := range b.RawEventStore.QueryEvents(nostr.Filter{IDs: []nostr.ID{id}}, 1) {
						yield(evt)
					}
				}
				return false
			})
		}
		if err != nil {
			return
		}
	}
}
