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

package database

import (
	"bytes"
	"errors"

	"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/hex"
)

// GetOrCreatePubkeySerial returns the serial for a pubkey, creating one if it doesn't exist.
// The pubkey parameter should be 32 bytes (schnorr public key).
// This function is thread-safe and uses transactions to ensure atomicity.
func (d *D) GetOrCreatePubkeySerial(pubkey []byte) (ser *types.Uint40, err error) {
	if len(pubkey) != 32 {
		err = errors.New("pubkey must be 32 bytes")
		return
	}

	// Create pubkey hash
	pubHash := new(types.PubHash)
	if err = pubHash.FromPubkey(pubkey); chk.E(err) {
		return
	}

	// First, try to get existing serial (separate transaction for read)
	var existingSer *types.Uint40
	existingSer, err = d.GetPubkeySerial(pubkey)
	if err == nil && existingSer != nil {
		// Serial already exists
		ser = existingSer
		return ser, nil
	}

	// Serial doesn't exist, create a new one
	var serial uint64
	if serial, err = d.pubkeySeq.Next(); chk.E(err) {
		return
	}

	ser = new(types.Uint40)
	if err = ser.Set(serial); chk.E(err) {
		return
	}

	// Store both mappings in a transaction
	err = d.Update(func(txn *badger.Txn) error {
		// Double-check that the serial wasn't created by another goroutine
		// while we were getting the sequence number
		prefixBuf := new(bytes.Buffer)
		prefixBuf.Write([]byte(indexes.PubkeySerialPrefix))
		if terr := pubHash.MarshalWrite(prefixBuf); chk.E(terr) {
			return terr
		}
		searchPrefix := prefixBuf.Bytes()

		opts := badger.DefaultIteratorOptions
		opts.PrefetchValues = false
		opts.Prefix = searchPrefix
		it := txn.NewIterator(opts)
		it.Seek(searchPrefix)
		if it.Valid() {
			// Another goroutine created it, extract and return that serial
			key := it.Item().KeyCopy(nil)
			it.Close()
			if len(key) == 16 {
				serialBytes := key[11:16]
				serialBuf := bytes.NewReader(serialBytes)
				existSer := new(types.Uint40)
				if terr := existSer.UnmarshalRead(serialBuf); terr == nil {
					ser = existSer
					return nil // Don't write, just return the existing serial
				}
			}
		}
		it.Close()

		// Store pubkey hash -> serial mapping
		keyBuf := new(bytes.Buffer)
		if terr := indexes.PubkeySerialEnc(pubHash, ser).MarshalWrite(keyBuf); chk.E(terr) {
			return terr
		}
		fullKey := make([]byte, len(keyBuf.Bytes()))
		copy(fullKey, keyBuf.Bytes())
		// DEBUG: log the key being written
		if len(fullKey) > 0 {
			// log.T.F("Writing PubkeySerial: key=%s (len=%d), prefix=%s", hex.Enc(fullKey), len(fullKey), string(fullKey[:3]))
		}
		if terr := txn.Set(fullKey, nil); chk.E(terr) {
			return terr
		}

		// Store serial -> full pubkey mapping (pubkey stored as value)
		keyBuf.Reset()
		if terr := indexes.SerialPubkeyEnc(ser).MarshalWrite(keyBuf); chk.E(terr) {
			return terr
		}
		if terr := txn.Set(keyBuf.Bytes(), pubkey); chk.E(terr) {
			return terr
		}

		return nil
	})

	return
}

// GetPubkeySerial returns the serial for a pubkey if it exists.
// Returns an error if the pubkey doesn't have a serial yet.
func (d *D) GetPubkeySerial(pubkey []byte) (ser *types.Uint40, err error) {
	if len(pubkey) != 32 {
		err = errors.New("pubkey must be 32 bytes")
		return
	}

	// Create pubkey hash
	pubHash := new(types.PubHash)
	if err = pubHash.FromPubkey(pubkey); chk.E(err) {
		return
	}

	// Build search key with just prefix + pubkey hash (no serial)
	prefixBuf := new(bytes.Buffer)
	prefixBuf.Write([]byte(indexes.PubkeySerialPrefix)) // 3 bytes
	if err = pubHash.MarshalWrite(prefixBuf); chk.E(err) {
		return
	}
	searchPrefix := prefixBuf.Bytes() // Should be 11 bytes: 3 (prefix) + 8 (pubkey hash)

	ser = new(types.Uint40)
	err = d.View(func(txn *badger.Txn) error {
		opts := badger.DefaultIteratorOptions
		opts.PrefetchValues = false // We only need the key
		it := txn.NewIterator(opts)
		defer it.Close()

		// Seek to the prefix and check if we found a matching key
		it.Seek(searchPrefix)
		if !it.ValidForPrefix(searchPrefix) {
			return errors.New("pubkey serial not found")
		}

		// Extract serial from key (last 5 bytes)
		// Key format: prefix(3) + pubkey_hash(8) + serial(5) = 16 bytes
		key := it.Item().KeyCopy(nil)
		if len(key) != 16 {
			return errors.New("invalid key length for pubkey serial")
		}

		// Verify the prefix matches
		if !bytes.HasPrefix(key, searchPrefix) {
			return errors.New("key prefix mismatch")
		}

		serialBytes := key[11:16] // Extract last 5 bytes (the serial)

		// Decode serial
		serialBuf := bytes.NewReader(serialBytes)
		if err := ser.UnmarshalRead(serialBuf); chk.E(err) {
			return err
		}

		return nil
	})

	return
}

// GetPubkeyBySerial returns the full 32-byte pubkey for a given serial.
func (d *D) GetPubkeyBySerial(ser *types.Uint40) (pubkey []byte, err error) {
	keyBuf := new(bytes.Buffer)
	if err = indexes.SerialPubkeyEnc(ser).MarshalWrite(keyBuf); chk.E(err) {
		return
	}

	err = d.View(func(txn *badger.Txn) error {
		item, gerr := txn.Get(keyBuf.Bytes())
		if chk.E(gerr) {
			return gerr
		}

		return item.Value(func(val []byte) error {
			pubkey = make([]byte, len(val))
			copy(pubkey, val)
			return nil
		})
	})

	if err != nil {
		err = errors.New("pubkey not found for serial: " + hex.Enc([]byte{byte(ser.Get())}))
	}

	return
}
