package binary

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"unsafe"

	"github.com/nbd-wtf/go-nostr"
)

// Deprecated -- the encoding used here is not very elegant, we'll have a better binary format later.
func Unmarshal(data []byte, evt *nostr.Event) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("failed to decode binary: %v", r)
		}
	}()

	evt.ID = hex.EncodeToString(data[0:32])
	evt.PubKey = hex.EncodeToString(data[32:64])
	evt.Sig = hex.EncodeToString(data[64:128])
	evt.CreatedAt = nostr.Timestamp(binary.BigEndian.Uint32(data[128:132]))
	evt.Kind = int(binary.BigEndian.Uint16(data[132:134]))
	contentLength := int(binary.BigEndian.Uint16(data[134:136]))
	evt.Content = unsafe.String(&data[136], contentLength)

	curr := 136 + contentLength

	nTags := binary.BigEndian.Uint16(data[curr : curr+2])
	curr++
	evt.Tags = make(nostr.Tags, nTags)

	for t := range evt.Tags {
		curr++
		nItems := int(data[curr])
		tag := make(nostr.Tag, nItems)
		for i := range tag {
			curr = curr + 1
			itemSize := int(binary.BigEndian.Uint16(data[curr : curr+2]))
			itemStart := curr + 2
			item := unsafe.String(&data[itemStart], itemSize)
			tag[i] = item
			curr = itemStart + itemSize
		}
		evt.Tags[t] = tag
	}

	return err
}

// Deprecated -- the encoding used here is not very elegant, we'll have a better binary format later.
func Marshal(evt *nostr.Event) ([]byte, error) {
	content := []byte(evt.Content)
	buf := make([]byte, 32+32+64+4+2+2+len(content)+65536+len(evt.Tags)*40 /* blergh */)

	hex.Decode(buf[0:32], []byte(evt.ID))
	hex.Decode(buf[32:64], []byte(evt.PubKey))
	hex.Decode(buf[64:128], []byte(evt.Sig))

	if evt.CreatedAt > MaxCreatedAt {
		return nil, fmt.Errorf("created_at is too big: %d, max is %d", evt.CreatedAt, MaxCreatedAt)
	}
	binary.BigEndian.PutUint32(buf[128:132], uint32(evt.CreatedAt))

	if evt.Kind > MaxKind {
		return nil, fmt.Errorf("kind is too big: %d, max is %d", evt.Kind, MaxKind)
	}
	binary.BigEndian.PutUint16(buf[132:134], uint16(evt.Kind))

	if contentLength := len(content); contentLength > MaxContentSize {
		return nil, fmt.Errorf("content is too large: %d, max is %d", contentLength, MaxContentSize)
	} else {
		binary.BigEndian.PutUint16(buf[134:136], uint16(contentLength))
	}
	copy(buf[136:], content)

	curr := 136 + len(content)

	if tagCount := len(evt.Tags); tagCount > MaxTagCount {
		return nil, fmt.Errorf("can't encode too many tags: %d, max is %d", tagCount, MaxTagCount)
	} else {
		binary.BigEndian.PutUint16(buf[curr:curr+2], uint16(tagCount))
	}
	curr++

	for _, tag := range evt.Tags {
		curr++
		if itemCount := len(tag); itemCount > MaxTagItemCount {
			return nil, fmt.Errorf("can't encode a tag with so many items: %d, max is %d", itemCount, MaxTagItemCount)
		} else {
			buf[curr] = uint8(itemCount)
		}
		for _, item := range tag {
			curr++
			itemb := []byte(item)
			itemSize := len(itemb)
			if itemSize > MaxTagItemSize {
				return nil, fmt.Errorf("tag item is too large: %d, max is %d", itemSize, MaxTagItemSize)
			}
			binary.BigEndian.PutUint16(buf[curr:curr+2], uint16(itemSize))
			itemEnd := curr + 2 + itemSize
			copy(buf[curr+2:itemEnd], itemb)
			curr = itemEnd
		}
	}
	buf = buf[0 : curr+1]
	return buf, nil
}