//go:build !(js && wasm)

package database

import (
	"bytes"
	"fmt"

	"github.com/dgraph-io/badger/v4"
	"lol.mleku.dev/chk"
	"next.orly.dev/pkg/database/indexes"
	"next.orly.dev/pkg/database/indexes/types"
	"git.mleku.dev/mleku/nostr/encoders/event"
)

// FetchEventBySerial fetches a single event by its serial.
// This function tries multiple storage formats in order:
// 1. cmp (compact format with serial references) - newest, most space-efficient
// 2. sev (small event inline) - legacy Reiser4 optimization
// 3. evt (traditional separate storage) - legacy fallback
func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
	// Create resolver for compact event decoding
	resolver := NewDatabaseSerialResolver(d, d.serialCache)

	if err = d.View(
		func(txn *badger.Txn) (err error) {
			// Try cmp (compact format) first - most efficient
			ev, err = d.fetchCompactEvent(txn, ser, resolver)
			if err == nil && ev != nil {
				return nil
			}
			err = nil // Reset error, try legacy formats

			// Helper function to extract inline event data from key
			extractInlineData := func(key []byte, prefixLen int) (*event.E, error) {
				if len(key) > prefixLen+2 {
					sizeIdx := prefixLen
					size := int(key[sizeIdx])<<8 | int(key[sizeIdx+1])
					dataStart := sizeIdx + 2

					if len(key) >= dataStart+size {
						eventData := key[dataStart : dataStart+size]

						// Check if this is compact format
						if len(eventData) > 0 && eventData[0] == CompactFormatVersion {
							eventId, idErr := d.GetEventIdBySerial(ser)
							if idErr != nil {
								// Cannot decode compact format without event ID - return error
								// DO NOT fall back to legacy unmarshal as compact format is not valid legacy format
								return nil, fmt.Errorf("compact format inline but no event ID mapping for serial %d: %w", ser.Get(), idErr)
							}
							return UnmarshalCompactEvent(eventData, eventId, resolver)
						}

						// Legacy binary format
						ev := new(event.E)
						if err := ev.UnmarshalBinary(bytes.NewBuffer(eventData)); err != nil {
							return nil, fmt.Errorf(
								"error unmarshaling inline event (size=%d): %w",
								size, err,
							)
						}
						return ev, nil
					}
				}
				return nil, nil
			}

			// Try sev (small event inline) prefix - Reiser4 optimization
			smallBuf := new(bytes.Buffer)
			if err = indexes.SmallEventEnc(ser).MarshalWrite(smallBuf); chk.E(err) {
				return
			}

			opts := badger.DefaultIteratorOptions
			opts.Prefix = smallBuf.Bytes()
			opts.PrefetchValues = true
			opts.PrefetchSize = 1
			it := txn.NewIterator(opts)
			defer it.Close()

			it.Rewind()
			if it.Valid() {
				// Found in sev table - extract inline data
				key := it.Item().Key()
				// Key format: sev|serial|size_uint16|event_data
				if ev, err = extractInlineData(key, 8); err != nil {
					return err
				}
				if ev != nil {
					return nil
				}
			}

			// Not found in sev table, try evt (traditional) prefix
			buf := new(bytes.Buffer)
			if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) {
				return
			}
			var item *badger.Item
			if item, err = txn.Get(buf.Bytes()); err != nil {
				return
			}
			var v []byte
			if v, err = item.ValueCopy(nil); chk.E(err) {
				return
			}

			// Check if this is compact format
			if len(v) > 0 && v[0] == CompactFormatVersion {
				eventId, idErr := d.GetEventIdBySerial(ser)
				if idErr != nil {
					// Cannot decode compact format without event ID - return error
					// DO NOT fall back to legacy unmarshal as compact format is not valid legacy format
					err = fmt.Errorf("compact format evt but no event ID mapping for serial %d: %w", ser.Get(), idErr)
					return
				}
				ev, err = UnmarshalCompactEvent(v, eventId, resolver)
				return
			}

			// Check if we have valid data before attempting to unmarshal
			if len(v) < 32+32+1+2+1+1+64 { // ID + Pubkey + min varint fields + Sig
				err = fmt.Errorf(
					"incomplete event data: got %d bytes, expected at least %d",
					len(v), 32+32+1+2+1+1+64,
				)
				return
			}
			ev = new(event.E)
			if err = ev.UnmarshalBinary(bytes.NewBuffer(v)); err != nil {
				// Add more context to EOF errors for debugging
				if err.Error() == "EOF" {
					err = fmt.Errorf(
						"EOF while unmarshaling event (serial=%v, data_len=%d): %w",
						ser, len(v), err,
					)
				}
				return
			}
			return
		},
	); err != nil {
		return
	}
	return
}
