package nip60

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"sync"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/decred/dcrd/dcrec/secp256k1/v4"
	"github.com/nbd-wtf/go-nostr"
)

type Wallet struct {
	wl *WalletStash

	Identifier  string
	Description string
	Name        string
	PrivateKey  *btcec.PrivateKey
	PublicKey   *btcec.PublicKey
	Relays      []string
	Mints       []string
	Tokens      []Token
	History     []HistoryEntry

	tokensMu sync.Mutex

	event                 *nostr.Event
	tokensPendingDeletion []string
}

func (w *Wallet) Balance() uint64 {
	var sum uint64
	for _, token := range w.Tokens {
		sum += token.Proofs.Amount()
	}
	return sum
}

func (w *Wallet) DisplayName() string {
	if w.Name != "" {
		return fmt.Sprintf("%s (%s)", w.Name, w.Identifier)
	}
	return w.Identifier
}

func (w *Wallet) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
	evt.CreatedAt = nostr.Now()
	evt.Kind = 37375
	evt.Tags = make(nostr.Tags, 0, 7)

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

	evt.Content, err = kr.Encrypt(
		ctx,
		fmt.Sprintf(`[["privkey","%x"]]`, w.PrivateKey.Serialize()),
		pk,
	)
	if err != nil {
		return err
	}

	evt.Tags = append(evt.Tags,
		nostr.Tag{"d", w.Identifier},
		nostr.Tag{"unit", "sat"},
	)
	if w.Name != "" {
		evt.Tags = append(evt.Tags, nostr.Tag{"name", w.Name})
	}
	if w.Description != "" {
		evt.Tags = append(evt.Tags, nostr.Tag{"description", w.Description})
	}
	for _, relay := range w.Relays {
		evt.Tags = append(evt.Tags, nostr.Tag{"relay", relay})
	}
	for _, mint := range w.Mints {
		evt.Tags = append(evt.Tags, nostr.Tag{"mint", mint})
	}

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

	return nil
}

func (w *Wallet) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
	w.Tokens = make([]Token, 0, 128)
	w.History = make([]HistoryEntry, 0, 128)
	w.event = evt

	pk, err := kr.GetPublicKey(ctx)
	if err != nil {
		return err
	}
	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...)
	}

	essential := 0
	for _, tag := range tags {
		if len(tag) < 2 {
			continue
		}
		switch tag[0] {
		case "d":
			essential++
			w.Identifier = tag[1]
		case "name":
			w.Name = tag[1]
		case "description":
			w.Description = tag[1]
		case "unit":
			essential++
			if tag[1] != "sat" {
				return fmt.Errorf("only 'sat' wallets are supported")
			}
		case "relay":
			w.Relays = append(w.Relays, tag[1])
		case "mint":
			w.Mints = append(w.Mints, tag[1])
		case "privkey":
			essential++
			skb, err := hex.DecodeString(tag[1])
			if err != nil {
				return fmt.Errorf("failed to parse private key: %w", err)
			}
			w.PrivateKey = secp256k1.PrivKeyFromBytes(skb)
			w.PublicKey = w.PrivateKey.PubKey()
		}
	}

	if essential != 3 {
		return fmt.Errorf("missing essential tags")
	}

	return nil
}
