package nip17

import (
	"context"
	"fmt"
	"strings"

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

func GetDMRelays(ctx context.Context, pubkey string, pool *nostr.SimplePool, relaysToQuery []string) []string {
	ie := pool.QuerySingle(ctx, relaysToQuery, nostr.Filter{
		Authors: []string{pubkey},
		Kinds:   []int{nostr.KindDMRelayList},
	})
	if ie == nil {
		return nil
	}

	res := make([]string, 0, 3)
	for _, tag := range ie.Tags {
		if len(tag) >= 2 && tag[0] == "relay" {
			res = append(res, tag[1])
			if len(res) == 3 {
				return res
			}
		}
	}

	return res
}

func PublishMessage(
	ctx context.Context,
	content string,
	tags nostr.Tags,
	pool *nostr.SimplePool,
	ourRelays []string,
	theirRelays []string,
	kr nostr.Keyer,
	recipientPubKey string,
	modify func(*nostr.Event),
) error {
	toUs, toThem, err := PrepareMessage(ctx, content, tags, kr, recipientPubKey, modify)
	if err != nil {
		return fmt.Errorf("failed to prepare message: %w", err)
	}

	sendErr := fmt.Errorf("failed to send event to ourselves in any of %v", ourRelays)
	publishOrAuth := func(ctx context.Context, url string, event nostr.Event) {
		r, err := pool.EnsureRelay(url)
		if err != nil {
			return
		}

		err = r.Publish(ctx, event)
		if err != nil && strings.HasPrefix(err.Error(), "auth-required:") {
			authErr := r.Auth(ctx, func(ae *nostr.Event) error { return kr.SignEvent(ctx, ae) })
			if authErr == nil {
				err = r.Publish(ctx, event)
			}
		}

		if err != nil {
			return
		}

		sendErr = nil
	}

	// send to ourselves
	for _, url := range ourRelays {
		publishOrAuth(ctx, url, toUs)
	}

	if sendErr != nil {
		return sendErr
	}

	// send to them
	sendErr = fmt.Errorf("failed to send event to them in any of %v", theirRelays)
	for _, url := range theirRelays {
		publishOrAuth(ctx, url, toThem)
	}

	return sendErr
}

func PrepareMessage(
	ctx context.Context,
	content string,
	tags nostr.Tags,
	kr nostr.Keyer,
	recipientPubKey string,
	modify func(*nostr.Event),
) (toUs nostr.Event, toThem nostr.Event, err error) {
	ourPubkey, err := kr.GetPublicKey(ctx)
	if err != nil {
		return nostr.Event{}, nostr.Event{}, err
	}

	rumor := nostr.Event{
		Kind:      nostr.KindDirectMessage,
		Content:   content,
		Tags:      append(tags, nostr.Tag{"p", recipientPubKey}),
		CreatedAt: nostr.Now(),
		PubKey:    ourPubkey,
	}
	rumor.ID = rumor.GetID()

	toUs, err = nip59.GiftWrap(
		rumor,
		ourPubkey,
		func(s string) (string, error) { return kr.Encrypt(ctx, s, ourPubkey) },
		func(e *nostr.Event) error { return kr.SignEvent(ctx, e) },
		modify,
	)
	if err != nil {
		return nostr.Event{}, nostr.Event{}, err
	}

	toThem, err = nip59.GiftWrap(
		rumor,
		recipientPubKey,
		func(s string) (string, error) { return kr.Encrypt(ctx, s, recipientPubKey) },
		func(e *nostr.Event) error { return kr.SignEvent(ctx, e) },
		modify,
	)
	if err != nil {
		return nostr.Event{}, nostr.Event{}, err
	}

	return toUs, toThem, nil
}

// ListenForMessages returns a channel with the rumors already decrypted and checked
func ListenForMessages(
	ctx context.Context,
	pool *nostr.SimplePool,
	kr nostr.Keyer,
	ourRelays []string,
	since nostr.Timestamp,
) chan nostr.Event {
	ch := make(chan nostr.Event)

	go func() {
		defer close(ch)

		pk, err := kr.GetPublicKey(ctx)
		if err != nil {
			nostr.InfoLogger.Printf("[nip17] failed to get public key from Keyer: %s\n", err)
			return
		}

		for ie := range pool.SubMany(ctx, ourRelays, nostr.Filters{
			{
				Kinds: []int{nostr.KindGiftWrap},
				Tags:  nostr.TagMap{"p": []string{pk}},
				Since: &since,
			},
		}) {
			rumor, err := nip59.GiftUnwrap(
				*ie.Event,
				func(otherpubkey, ciphertext string) (string, error) { return kr.Decrypt(ctx, ciphertext, otherpubkey) },
			)
			if err != nil {
				nostr.InfoLogger.Printf("[nip17] failed to unwrap received message '%s' from %s: %s\n", ie.Event, ie.Relay.URL, err)
				continue
			}

			ch <- rumor
		}
	}()

	return ch
}
