package nip60

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"

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

type HistoryEntry struct {
	In     bool // in = received, out = sent
	Amount uint64

	TokenReferences []TokenRef

	createdAt nostr.Timestamp
	event     *nostr.Event
}

type TokenRef struct {
	EventID  string
	Created  bool
	IsNutzap bool
}

func (h HistoryEntry) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
	pk, err := kr.GetPublicKey(ctx)
	if err != nil {
		return err
	}

	dir := "in"
	if !h.In {
		dir = "out"
	}

	evt.CreatedAt = h.createdAt
	evt.Kind = 7376
	evt.Tags = nostr.Tags{}

	encryptedTags := nostr.Tags{
		nostr.Tag{"direction", dir},
		nostr.Tag{"amount", strconv.FormatUint(uint64(h.Amount), 10)},
	}

	for _, tf := range h.TokenReferences {
		if tf.IsNutzap {
			evt.Tags = append(evt.Tags, nostr.Tag{"e", tf.EventID, "", "redeemed"})
			continue
		}

		marker := "destroyed"
		if tf.Created {
			marker = "created"
		}

		encryptedTags = append(encryptedTags, nostr.Tag{"e", tf.EventID, "", marker})
	}

	jsonb, _ := json.Marshal(encryptedTags)
	evt.Content, err = kr.Encrypt(
		ctx,
		string(jsonb),
		pk,
	)
	if err != nil {
		return err
	}

	err = kr.SignEvent(ctx, evt)
	if err != nil {
		return err
	}

	return nil
}

func (h *HistoryEntry) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
	h.event = evt
	h.createdAt = evt.CreatedAt
	h.TokenReferences = make([]TokenRef, 0, 3)

	pk, err := kr.GetPublicKey(ctx)
	if err != nil {
		return err
	}

	// event tags and encrypted tags are mixed together
	jsonb, err := kr.Decrypt(ctx, evt.Content, pk)
	if err != nil {
		return err
	}
	var tags nostr.Tags
	if len(jsonb) > 0 {
		tags = make(nostr.Tags, 0, 7)
		if err := json.Unmarshal([]byte(jsonb), &tags); err != nil {
			return err
		}
		tags = append(tags, evt.Tags...)
	}

	missingDirection := true
	for _, tag := range tags {
		if len(tag) < 2 {
			continue
		}
		switch tag[0] {
		case "direction":
			missingDirection = false
			if tag[1] == "in" {
				h.In = true
			} else if tag[1] == "out" {
				h.In = false
			} else {
				return fmt.Errorf("unexpected 'direction' tag %s", tag[1])
			}
		case "amount":
			if len(tag) < 2 {
				return fmt.Errorf("'amount' tag must have at least 2 items")
			}
			v, err := strconv.ParseUint(tag[1], 10, 64)
			if err != nil {
				return fmt.Errorf("invalid 'amount' %s: %w", tag[1], err)
			}
			h.Amount = v
		case "e":
			if len(tag) < 4 {
				return fmt.Errorf("'e' tag must have at least 4 items")
			}
			if !nostr.IsValid32ByteHex(tag[1]) {
				return fmt.Errorf("'e' tag has invalid event id %s", tag[1])
			}

			tf := TokenRef{EventID: tag[1]}
			switch tag[3] {
			case "created":
				tf.Created = true
			case "destroyed":
				tf.Created = false
			case "redeemed":
				tf.IsNutzap = true
				tf.Created = true
			default:
				return fmt.Errorf("unsupported 'e' token marker: %s", tag[3])
			}
			h.TokenReferences = append(h.TokenReferences, tf)
		}
	}

	if h.Amount == 0 {
		return fmt.Errorf("missing 'amount' tag")
	}

	if missingDirection {
		return fmt.Errorf("missing 'direction' tag")
	}

	return nil
}