package nostr

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/valyala/fastjson"
)

func (evt *Event) UnmarshalJSON(payload []byte) error {
	var fastjsonParser fastjson.Parser
	parsed, err := fastjsonParser.ParseBytes(payload)
	if err != nil {
		return fmt.Errorf("failed to parse event: %w", err)
	}

	obj, err := parsed.Object()
	if err != nil {
		return fmt.Errorf("event is not an object")
	}

	// prepare this to receive any extra property that may serialized along with the event
	evt.extra = make(map[string]any)

	var visiterr error
	obj.Visit(func(k []byte, v *fastjson.Value) {
		key := string(k)
		switch key {
		case "id":
			id, err := v.StringBytes()
			if err != nil {
				visiterr = fmt.Errorf("invalid 'id' field: %w", err)
			}
			evt.ID = string(id)
		case "pubkey":
			id, err := v.StringBytes()
			if err != nil {
				visiterr = fmt.Errorf("invalid 'pubkey' field: %w", err)
			}
			evt.PubKey = string(id)
		case "created_at":
			val, err := v.Int64()
			if err != nil {
				visiterr = fmt.Errorf("invalid 'created_at' field: %w", err)
			}
			evt.CreatedAt = time.Unix(val, 0)
		case "kind":
			kind, err := v.Int64()
			if err != nil {
				visiterr = fmt.Errorf("invalid 'kind' field: %w", err)
			}
			evt.Kind = int(kind)
		case "tags":
			evt.Tags, err = fastjsonArrayToTags(v)
			if err != nil {
				visiterr = fmt.Errorf("invalid '%s' field: %w", key, err)
			}
		case "content":
			id, err := v.StringBytes()
			if err != nil {
				visiterr = fmt.Errorf("invalid 'content' field: %w", err)
			}
			evt.Content = string(id)
		case "sig":
			id, err := v.StringBytes()
			if err != nil {
				visiterr = fmt.Errorf("invalid 'sig' field: %w", err)
			}
			evt.Sig = string(id)
		default:
			var anyValue any
			json.Unmarshal(v.MarshalTo([]byte{}), &anyValue)
			evt.extra[key] = anyValue
		}
	})
	if visiterr != nil {
		return visiterr
	}

	return nil
}

func (evt Event) MarshalJSON() ([]byte, error) {
	var arena fastjson.Arena

	o := arena.NewObject()
	o.Set("id", arena.NewString(evt.ID))
	o.Set("pubkey", arena.NewString(evt.PubKey))
	o.Set("created_at", arena.NewNumberInt(int(evt.CreatedAt.Unix())))
	o.Set("kind", arena.NewNumberInt(evt.Kind))
	o.Set("tags", tagsToFastjsonArray(&arena, evt.Tags))
	o.Set("content", arena.NewString(evt.Content))
	o.Set("sig", arena.NewString(evt.Sig))

	for k, v := range evt.extra {
		b, _ := json.Marshal(v)
		if val, err := fastjson.ParseBytes(b); err == nil {
			o.Set(k, val)
		}
	}

	return o.MarshalTo(nil), nil
}

func fastjsonArrayToTags(v *fastjson.Value) (Tags, error) {
	arr, err := v.Array()
	if err != nil {
		return nil, err
	}

	sll := make(Tags, len(arr))
	for i, v := range arr {
		subarr, err := v.Array()
		if err != nil {
			return nil, err
		}

		sl := make(Tag, len(subarr))
		for j, subv := range subarr {
			sb, err := subv.StringBytes()
			if err != nil {
				return nil, err
			}
			sl[j] = string(sb)
		}
		sll[i] = sl
	}

	return sll, nil
}

func tagsToFastjsonArray(arena *fastjson.Arena, tags Tags) *fastjson.Value {
	jtags := arena.NewArray()
	for i, v := range tags {
		arr := arena.NewArray()
		for j, subv := range v {
			arr.SetArrayItem(j, arena.NewString(subv))
		}
		jtags.SetArrayItem(i, arr)
	}
	return jtags
}