//go:build js && wasm

package wasmdb

import (
	"bytes"
	"context"
	"errors"
	"sort"
	"strconv"
	"time"

	"github.com/aperturerobotics/go-indexeddb/idb"
	"lol.mleku.dev/chk"
	sha256 "github.com/minio/sha256-simd"

	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/filter"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/ints"
	"git.mleku.dev/mleku/nostr/encoders/kind"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"next.orly.dev/pkg/database"
	"next.orly.dev/pkg/database/indexes/types"
	"next.orly.dev/pkg/interfaces/store"
	"next.orly.dev/pkg/utils"
)

// CheckExpiration checks if an event has expired based on its "expiration" tag
func CheckExpiration(ev *event.E) (expired bool) {
	var err error
	expTag := ev.Tags.GetFirst([]byte("expiration"))
	if expTag != nil {
		expTS := ints.New(0)
		if _, err = expTS.Unmarshal(expTag.Value()); err == nil {
			if int64(expTS.N) < time.Now().Unix() {
				return true
			}
		}
	}
	return
}

// GetSerialsByRange retrieves serials from an index range using cursor iteration.
// The index keys must end with a 5-byte serial number.
func (w *W) GetSerialsByRange(idx database.Range) (sers types.Uint40s, err error) {
	if len(idx.Start) < 3 {
		return nil, errors.New("invalid range: start key too short")
	}

	// Extract the object store name from the 3-byte prefix
	storeName := string(idx.Start[:3])

	// Open a read transaction
	tx, err := w.db.Transaction(idb.TransactionReadOnly, storeName)
	if err != nil {
		return nil, err
	}

	objStore, err := tx.ObjectStore(storeName)
	if err != nil {
		return nil, err
	}

	// Open cursor in reverse order (newest first like Badger)
	cursorReq, err := objStore.OpenCursor(idb.CursorPrevious)
	if err != nil {
		return nil, err
	}

	// Pre-allocate slice
	sers = make(types.Uint40s, 0, 100)

	// Create end boundary with 0xff suffix for inclusive range
	endBoundary := make([]byte, len(idx.End)+5)
	copy(endBoundary, idx.End)
	for i := len(idx.End); i < len(endBoundary); i++ {
		endBoundary[i] = 0xff
	}

	err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
		keyVal, keyErr := cursor.Key()
		if keyErr != nil {
			return keyErr
		}

		key := safeValueToBytes(keyVal)
		if len(key) < 8 { // minimum: 3 prefix + 5 serial
			return cursor.Continue()
		}

		// Check if key is within range
		keyWithoutSerial := key[:len(key)-5]

		// Compare with start (lower bound)
		cmp := bytes.Compare(keyWithoutSerial, idx.Start)
		if cmp < 0 {
			// Key is before range start, stop iteration
			return errors.New("done")
		}

		// Compare with end boundary
		if bytes.Compare(key, endBoundary) > 0 {
			// Key is after range end, continue to find keys in range
			return cursor.Continue()
		}

		// Extract serial from last 5 bytes
		ser := new(types.Uint40)
		if err := ser.UnmarshalRead(bytes.NewReader(key[len(key)-5:])); err == nil {
			sers = append(sers, ser)
		}

		return cursor.Continue()
	})

	if err != nil && err.Error() != "done" {
		return nil, err
	}

	// Sort by serial (ascending)
	sort.Slice(sers, func(i, j int) bool {
		return sers[i].Get() < sers[j].Get()
	})

	return sers, nil
}

// QueryForIds retrieves IdPkTs records based on a filter.
// Results are sorted by timestamp in reverse chronological order.
func (w *W) QueryForIds(c context.Context, f *filter.F) (idPkTs []*store.IdPkTs, err error) {
	if f.Ids != nil && f.Ids.Len() > 0 {
		err = errors.New("query for Ids is invalid for a filter with Ids")
		return
	}

	var idxs []database.Range
	if idxs, err = database.GetIndexesFromFilter(f); chk.E(err) {
		return
	}

	var results []*store.IdPkTs
	results = make([]*store.IdPkTs, 0, len(idxs)*100)

	// Track match counts for search ranking
	counts := make(map[uint64]int)

	for _, idx := range idxs {
		var founds types.Uint40s
		if founds, err = w.GetSerialsByRange(idx); err != nil {
			w.Logger.Warnf("QueryForIds: GetSerialsByRange error: %v", err)
			continue
		}

		var tmp []*store.IdPkTs
		if tmp, err = w.GetFullIdPubkeyBySerials(founds); err != nil {
			w.Logger.Warnf("QueryForIds: GetFullIdPubkeyBySerials error: %v", err)
			continue
		}

		// Track match counts for search queries
		if len(f.Search) > 0 {
			for _, v := range tmp {
				counts[v.Ser]++
			}
		}
		results = append(results, tmp...)
	}

	// Deduplicate results
	seen := make(map[uint64]struct{}, len(results))
	idPkTs = make([]*store.IdPkTs, 0, len(results))
	for _, idpk := range results {
		if _, ok := seen[idpk.Ser]; !ok {
			seen[idpk.Ser] = struct{}{}
			idPkTs = append(idPkTs, idpk)
		}
	}

	// For search queries combined with other filters, verify matches
	if len(f.Search) > 0 && ((f.Authors != nil && f.Authors.Len() > 0) ||
		(f.Kinds != nil && f.Kinds.Len() > 0) ||
		(f.Tags != nil && f.Tags.Len() > 0)) {
		// Build serial list for fetching
		serials := make([]*types.Uint40, 0, len(idPkTs))
		for _, v := range idPkTs {
			s := new(types.Uint40)
			s.Set(v.Ser)
			serials = append(serials, s)
		}

		var evs map[uint64]*event.E
		if evs, err = w.FetchEventsBySerials(serials); chk.E(err) {
			return
		}

		filtered := make([]*store.IdPkTs, 0, len(idPkTs))
		for _, v := range idPkTs {
			ev, ok := evs[v.Ser]
			if !ok || ev == nil {
				continue
			}

			matchesAll := true
			if f.Authors != nil && f.Authors.Len() > 0 && !f.Authors.Contains(ev.Pubkey) {
				matchesAll = false
			}
			if matchesAll && f.Kinds != nil && f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) {
				matchesAll = false
			}
			if matchesAll && f.Tags != nil && f.Tags.Len() > 0 {
				tagOK := true
				for _, t := range *f.Tags {
					if t.Len() < 2 {
						continue
					}
					key := t.Key()
					values := t.T[1:]
					if !ev.Tags.ContainsAny(key, values) {
						tagOK = false
						break
					}
				}
				if !tagOK {
					matchesAll = false
				}
			}
			if matchesAll {
				filtered = append(filtered, v)
			}
		}
		idPkTs = filtered
	}

	// Sort by timestamp (newest first)
	if len(f.Search) == 0 {
		sort.Slice(idPkTs, func(i, j int) bool {
			return idPkTs[i].Ts > idPkTs[j].Ts
		})
	} else {
		// Search ranking: blend match count with recency
		var maxCount int
		var minTs, maxTs int64
		if len(idPkTs) > 0 {
			minTs, maxTs = idPkTs[0].Ts, idPkTs[0].Ts
		}
		for _, v := range idPkTs {
			if c := counts[v.Ser]; c > maxCount {
				maxCount = c
			}
			if v.Ts < minTs {
				minTs = v.Ts
			}
			if v.Ts > maxTs {
				maxTs = v.Ts
			}
		}
		tsSpan := maxTs - minTs
		if tsSpan <= 0 {
			tsSpan = 1
		}
		if maxCount <= 0 {
			maxCount = 1
		}
		sort.Slice(idPkTs, func(i, j int) bool {
			ci := float64(counts[idPkTs[i].Ser]) / float64(maxCount)
			cj := float64(counts[idPkTs[j].Ser]) / float64(maxCount)
			ai := float64(idPkTs[i].Ts-minTs) / float64(tsSpan)
			aj := float64(idPkTs[j].Ts-minTs) / float64(tsSpan)
			si := 0.5*ci + 0.5*ai
			sj := 0.5*cj + 0.5*aj
			if si == sj {
				return idPkTs[i].Ts > idPkTs[j].Ts
			}
			return si > sj
		})
	}

	// Apply limit
	if f.Limit != nil && len(idPkTs) > int(*f.Limit) {
		idPkTs = idPkTs[:*f.Limit]
	}

	return
}

// QueryForSerials takes a filter and returns matching event serials
func (w *W) QueryForSerials(c context.Context, f *filter.F) (sers types.Uint40s, err error) {
	var founds []*types.Uint40
	var idPkTs []*store.IdPkTs

	if f.Ids != nil && f.Ids.Len() > 0 {
		// Use batch lookup for IDs
		var serialMap map[string]*types.Uint40
		if serialMap, err = w.GetSerialsByIds(f.Ids); chk.E(err) {
			return
		}
		for _, ser := range serialMap {
			founds = append(founds, ser)
		}
		var tmp []*store.IdPkTs
		if tmp, err = w.GetFullIdPubkeyBySerials(founds); chk.E(err) {
			return
		}
		idPkTs = append(idPkTs, tmp...)
	} else {
		if idPkTs, err = w.QueryForIds(c, f); chk.E(err) {
			return
		}
	}

	// Extract serials
	for _, idpk := range idPkTs {
		ser := new(types.Uint40)
		if err = ser.Set(idpk.Ser); chk.E(err) {
			continue
		}
		sers = append(sers, ser)
	}
	return
}

// QueryEvents queries events based on a filter
func (w *W) QueryEvents(c context.Context, f *filter.F) (evs event.S, err error) {
	return w.QueryEventsWithOptions(c, f, true, false)
}

// QueryAllVersions queries events and returns all versions of replaceable events
func (w *W) QueryAllVersions(c context.Context, f *filter.F) (evs event.S, err error) {
	return w.QueryEventsWithOptions(c, f, true, true)
}

// QueryEventsWithOptions queries events with additional options for deletion and versioning
func (w *W) QueryEventsWithOptions(c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool) (evs event.S, err error) {
	wantMultipleVersions := showAllVersions || (f.Limit != nil && *f.Limit > 1)

	var expDeletes types.Uint40s
	var expEvs event.S

	// Handle ID-based queries
	if f.Ids != nil && f.Ids.Len() > 0 {
		w.Logger.Debugf("QueryEvents: ids path, count=%d", f.Ids.Len())

		serials, idErr := w.GetSerialsByIds(f.Ids)
		if idErr != nil {
			w.Logger.Warnf("QueryEvents: error looking up ids: %v", idErr)
		}

		// Convert to slice for batch fetch
		var serialsSlice []*types.Uint40
		idHexToSerial := make(map[uint64]string, len(serials))
		for idHex, ser := range serials {
			serialsSlice = append(serialsSlice, ser)
			idHexToSerial[ser.Get()] = idHex
		}

		// Batch fetch events
		var fetchedEvents map[uint64]*event.E
		if fetchedEvents, err = w.FetchEventsBySerials(serialsSlice); err != nil {
			w.Logger.Warnf("QueryEvents: batch fetch failed: %v", err)
			return
		}

		// Process fetched events
		for serialValue, ev := range fetchedEvents {
			idHex := idHexToSerial[serialValue]

			ser := new(types.Uint40)
			if err = ser.Set(serialValue); err != nil {
				continue
			}

			// Check expiration
			if CheckExpiration(ev) {
				w.Logger.Debugf("QueryEvents: id=%s filtered out due to expiration", idHex)
				expDeletes = append(expDeletes, ser)
				expEvs = append(expEvs, ev)
				continue
			}

			// Check for deletion
			if derr := w.CheckForDeleted(ev, nil); derr != nil {
				w.Logger.Debugf("QueryEvents: id=%s filtered out due to deletion: %v", idHex, derr)
				continue
			}

			evs = append(evs, ev)
		}

		// Sort and apply limit
		sort.Slice(evs, func(i, j int) bool {
			return evs[i].CreatedAt > evs[j].CreatedAt
		})
		if f.Limit != nil && len(evs) > int(*f.Limit) {
			evs = evs[:*f.Limit]
		}
	} else {
		// Non-IDs path
		var idPkTs []*store.IdPkTs
		if idPkTs, err = w.QueryForIds(c, f); chk.E(err) {
			return
		}

		// Maps for replaceable event handling
		replaceableEvents := make(map[string]*event.E)
		replaceableEventVersions := make(map[string]event.S)
		paramReplaceableEvents := make(map[string]map[string]*event.E)
		paramReplaceableEventVersions := make(map[string]map[string]event.S)
		var regularEvents event.S

		// Deletion tracking maps
		deletionsByKindPubkey := make(map[string]bool)
		deletionsByKindPubkeyDTag := make(map[string]map[string]int64)
		deletedEventIds := make(map[string]bool)

		// Query for deletion events if we have authors
		if f.Authors != nil && f.Authors.Len() > 0 {
			deletionFilter := &filter.F{
				Kinds:   kind.NewS(kind.New(5)),
				Authors: f.Authors,
			}
			var deletionIdPkTs []*store.IdPkTs
			if deletionIdPkTs, err = w.QueryForIds(c, deletionFilter); err == nil {
				idPkTs = append(idPkTs, deletionIdPkTs...)
			}
		}

		// Prepare serials for batch fetch
		var allSerials []*types.Uint40
		serialToIdPk := make(map[uint64]*store.IdPkTs, len(idPkTs))
		for _, idpk := range idPkTs {
			ser := new(types.Uint40)
			if err = ser.Set(idpk.Ser); err != nil {
				continue
			}
			allSerials = append(allSerials, ser)
			serialToIdPk[ser.Get()] = idpk
		}

		// Batch fetch all events
		var allEvents map[uint64]*event.E
		if allEvents, err = w.FetchEventsBySerials(allSerials); err != nil {
			w.Logger.Warnf("QueryEvents: batch fetch failed in non-IDs path: %v", err)
			return
		}

		// First pass: collect deletion events
		for serialValue, ev := range allEvents {
			ser := new(types.Uint40)
			if err = ser.Set(serialValue); err != nil {
				continue
			}

			if CheckExpiration(ev) {
				expDeletes = append(expDeletes, ser)
				expEvs = append(expEvs, ev)
				continue
			}

			if ev.Kind == kind.Deletion.K {
				// Process e-tags and a-tags for deletion tracking
				aTags := ev.Tags.GetAll([]byte("a"))
				for _, aTag := range aTags {
					if aTag.Len() < 2 {
						continue
					}
					split := bytes.Split(aTag.Value(), []byte{':'})
					if len(split) < 2 {
						continue
					}
					kindInt, parseErr := strconv.Atoi(string(split[0]))
					if parseErr != nil {
						continue
					}
					kk := kind.New(uint16(kindInt))
					if !kind.IsReplaceable(kk.K) {
						continue
					}
					var pk []byte
					if pk, err = hex.DecAppend(nil, split[1]); err != nil {
						continue
					}
					if !utils.FastEqual(pk, ev.Pubkey) {
						continue
					}
					key := hex.Enc(pk) + ":" + strconv.Itoa(int(kk.K))

					if kind.IsParameterizedReplaceable(kk.K) {
						if len(split) < 3 {
							continue
						}
						if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
							deletionsByKindPubkeyDTag[key] = make(map[string]int64)
						}
						dValue := string(split[2])
						if ts, ok := deletionsByKindPubkeyDTag[key][dValue]; !ok || ev.CreatedAt > ts {
							deletionsByKindPubkeyDTag[key][dValue] = ev.CreatedAt
						}
					} else {
						deletionsByKindPubkey[key] = true
					}
				}

				// Process e-tags for specific event deletions
				eTags := ev.Tags.GetAll([]byte("e"))
				for _, eTag := range eTags {
					eTagHex := eTag.ValueHex()
					if len(eTagHex) != 64 {
						continue
					}
					evId := make([]byte, sha256.Size)
					if _, hexErr := hex.DecBytes(evId, eTagHex); hexErr != nil {
						continue
					}

					// Look for target in current batch
					var targetEv *event.E
					for _, candidateEv := range allEvents {
						if utils.FastEqual(candidateEv.ID, evId) {
							targetEv = candidateEv
							break
						}
					}

					// Try to fetch if not in batch
					if targetEv == nil {
						ser, serErr := w.GetSerialById(evId)
						if serErr != nil || ser == nil {
							continue
						}
						targetEv, serErr = w.FetchEventBySerial(ser)
						if serErr != nil || targetEv == nil {
							continue
						}
					}

					if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) {
						continue
					}
					deletedEventIds[hex.Enc(targetEv.ID)] = true
				}
			}
		}

		// Second pass: process all events, filtering deleted ones
		for _, ev := range allEvents {
			// Tag filter verification
			if f.Tags != nil && f.Tags.Len() > 0 {
				tagMatches := 0
				for _, filterTag := range *f.Tags {
					if filterTag.Len() >= 2 {
						filterKey := filterTag.Key()
						var actualKey []byte
						if len(filterKey) == 2 && filterKey[0] == '#' {
							actualKey = filterKey[1:]
						} else {
							actualKey = filterKey
						}
						eventHasTag := false
						if ev.Tags != nil {
							for _, eventTag := range *ev.Tags {
								if eventTag.Len() >= 2 && bytes.Equal(eventTag.Key(), actualKey) {
									for _, filterValue := range filterTag.T[1:] {
										if database.TagValuesMatchUsingTagMethods(eventTag, filterValue) {
											eventHasTag = true
											break
										}
									}
									if eventHasTag {
										break
									}
								}
							}
						}
						if eventHasTag {
							tagMatches++
						}
					}
				}
				if tagMatches < f.Tags.Len() {
					continue
				}
			}

			// Skip deletion events unless explicitly requested
			if ev.Kind == kind.Deletion.K {
				kind5Requested := false
				if f.Kinds != nil && f.Kinds.Len() > 0 {
					for i := 0; i < f.Kinds.Len(); i++ {
						if f.Kinds.K[i].K == kind.Deletion.K {
							kind5Requested = true
							break
						}
					}
				}
				if !kind5Requested {
					continue
				}
			}

			// Check if event ID is in filter
			isIdInFilter := false
			if f.Ids != nil && f.Ids.Len() > 0 {
				for i := 0; i < f.Ids.Len(); i++ {
					if utils.FastEqual(ev.ID, (*f.Ids).T[i]) {
						isIdInFilter = true
						break
					}
				}
			}

			// Check if specifically deleted
			eventIdHex := hex.Enc(ev.ID)
			if deletedEventIds[eventIdHex] {
				continue
			}

			// Handle replaceable events
			if kind.IsReplaceable(ev.Kind) {
				key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
				if deletionsByKindPubkey[key] && !isIdInFilter {
					continue
				} else if wantMultipleVersions {
					replaceableEventVersions[key] = append(replaceableEventVersions[key], ev)
				} else {
					existing, exists := replaceableEvents[key]
					if !exists || ev.CreatedAt > existing.CreatedAt {
						replaceableEvents[key] = ev
					}
				}
			} else if kind.IsParameterizedReplaceable(ev.Kind) {
				key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
				dTag := ev.Tags.GetFirst([]byte("d"))
				var dValue string
				if dTag != nil && dTag.Len() > 1 {
					dValue = string(dTag.Value())
				}

				if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists {
					if delTs, ok := deletionMap[dValue]; ok && ev.CreatedAt < delTs && !isIdInFilter {
						continue
					}
				}

				if wantMultipleVersions {
					if _, exists := paramReplaceableEventVersions[key]; !exists {
						paramReplaceableEventVersions[key] = make(map[string]event.S)
					}
					paramReplaceableEventVersions[key][dValue] = append(paramReplaceableEventVersions[key][dValue], ev)
				} else {
					if _, exists := paramReplaceableEvents[key]; !exists {
						paramReplaceableEvents[key] = make(map[string]*event.E)
					}
					existing, exists := paramReplaceableEvents[key][dValue]
					if !exists || ev.CreatedAt > existing.CreatedAt {
						paramReplaceableEvents[key][dValue] = ev
					}
				}
			} else {
				regularEvents = append(regularEvents, ev)
			}
		}

		// Collect results
		if wantMultipleVersions {
			for _, versions := range replaceableEventVersions {
				sort.Slice(versions, func(i, j int) bool {
					return versions[i].CreatedAt > versions[j].CreatedAt
				})
				limit := len(versions)
				if f.Limit != nil && int(*f.Limit) < limit {
					limit = int(*f.Limit)
				}
				for i := 0; i < limit; i++ {
					evs = append(evs, versions[i])
				}
			}
		} else {
			for _, ev := range replaceableEvents {
				evs = append(evs, ev)
			}
		}

		if wantMultipleVersions {
			for _, dTagMap := range paramReplaceableEventVersions {
				for _, versions := range dTagMap {
					sort.Slice(versions, func(i, j int) bool {
						return versions[i].CreatedAt > versions[j].CreatedAt
					})
					limit := len(versions)
					if f.Limit != nil && int(*f.Limit) < limit {
						limit = int(*f.Limit)
					}
					for i := 0; i < limit; i++ {
						evs = append(evs, versions[i])
					}
				}
			}
		} else {
			for _, innerMap := range paramReplaceableEvents {
				for _, ev := range innerMap {
					evs = append(evs, ev)
				}
			}
		}

		evs = append(evs, regularEvents...)

		// Sort and limit
		sort.Slice(evs, func(i, j int) bool {
			return evs[i].CreatedAt > evs[j].CreatedAt
		})
		if f.Limit != nil && len(evs) > int(*f.Limit) {
			evs = evs[:*f.Limit]
		}

		// Delete expired events in background
		go func() {
			for i, ser := range expDeletes {
				w.DeleteEventBySerial(context.Background(), ser, expEvs[i])
			}
		}()
	}

	return
}

// QueryDeleteEventsByTargetId queries for delete events targeting a specific event ID
func (w *W) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (evs event.S, err error) {
	f := &filter.F{
		Kinds: kind.NewS(kind.Deletion),
		Tags: tag.NewS(
			tag.NewFromAny("#e", hex.Enc(targetEventId)),
		),
	}
	return w.QueryEventsWithOptions(c, f, true, false)
}

// CountEvents counts events matching a filter
func (w *W) CountEvents(c context.Context, f *filter.F) (count int, approx bool, err error) {
	approx = false
	if f == nil {
		return 0, false, nil
	}

	// For ID-based queries, count resolved IDs
	if f.Ids != nil && f.Ids.Len() > 0 {
		serials, idErr := w.GetSerialsByIds(f.Ids)
		if idErr != nil {
			return 0, false, idErr
		}
		return len(serials), false, nil
	}

	// For other queries, get serials and count
	var sers types.Uint40s
	if sers, err = w.QueryForSerials(c, f); err != nil {
		return 0, false, err
	}

	return len(sers), false, nil
}

// GetSerialsFromFilter is an alias for QueryForSerials for interface compatibility
func (w *W) GetSerialsFromFilter(f *filter.F) (serials types.Uint40s, err error) {
	return w.QueryForSerials(w.ctx, f)
}
