package app

import (
	"context"
	// std hex not used; use project hex encoder instead
	"fmt"
	"strings"
	"sync"
	"time"

	"encoding/json"

	"github.com/dgraph-io/badger/v4"
	"lol.mleku.dev/chk"
	"lol.mleku.dev/log"
	"next.orly.dev/app/config"
	"next.orly.dev/pkg/acl"
	"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
	"next.orly.dev/pkg/database"
	"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/kind"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"git.mleku.dev/mleku/nostr/encoders/timestamp"
	"next.orly.dev/pkg/protocol/nwc"
)

// PaymentProcessor handles NWC payment notifications and updates subscriptions
type PaymentProcessor struct {
	nwcClient    *nwc.Client
	db           *database.D
	config       *config.C
	ctx          context.Context
	cancel       context.CancelFunc
	wg           sync.WaitGroup
	dashboardURL string
}

// NewPaymentProcessor creates a new payment processor
func NewPaymentProcessor(
	ctx context.Context, cfg *config.C, db *database.D,
) (pp *PaymentProcessor, err error) {
	if cfg.NWCUri == "" {
		return nil, fmt.Errorf("NWC URI not configured")
	}

	var nwcClient *nwc.Client
	if nwcClient, err = nwc.NewClient(cfg.NWCUri); chk.E(err) {
		return nil, fmt.Errorf("failed to create NWC client: %w", err)
	}

	c, cancel := context.WithCancel(ctx)

	pp = &PaymentProcessor{
		nwcClient: nwcClient,
		db:        db,
		config:    cfg,
		ctx:       c,
		cancel:    cancel,
	}

	return pp, nil
}

// Start begins listening for payment notifications
func (pp *PaymentProcessor) Start() error {
	// start NWC notifications listener
	pp.wg.Add(1)
	go func() {
		defer pp.wg.Done()
		if err := pp.listenForPayments(); err != nil {
			log.E.F("payment processor error: %v", err)
		}
	}()
	// start periodic follow-list sync if subscriptions are enabled
	if pp.config != nil && pp.config.SubscriptionEnabled {
		pp.wg.Add(1)
		go func() {
			defer pp.wg.Done()
			pp.runFollowSyncLoop()
		}()
		// start daily subscription checker
		pp.wg.Add(1)
		go func() {
			defer pp.wg.Done()
			pp.runDailySubscriptionChecker()
		}()
	}
	return nil
}

// Stop gracefully stops the payment processor
func (pp *PaymentProcessor) Stop() {
	if pp.cancel != nil {
		pp.cancel()
	}
	pp.wg.Wait()
}

// listenForPayments subscribes to NWC notifications and processes payments
func (pp *PaymentProcessor) listenForPayments() error {
	return pp.nwcClient.SubscribeNotifications(pp.ctx, pp.handleNotification)
}

// runFollowSyncLoop periodically syncs the relay identity follow list with active subscribers
func (pp *PaymentProcessor) runFollowSyncLoop() {
	t := time.NewTicker(10 * time.Minute)
	defer t.Stop()
	// do an initial sync shortly after start
	_ = pp.syncFollowList()
	for {
		select {
		case <-pp.ctx.Done():
			return
		case <-t.C:
			if err := pp.syncFollowList(); err != nil {
				log.W.F("follow list sync failed: %v", err)
			}
		}
	}
}

// runDailySubscriptionChecker checks once daily for subscription expiry warnings and trial reminders
func (pp *PaymentProcessor) runDailySubscriptionChecker() {
	t := time.NewTicker(24 * time.Hour)
	defer t.Stop()
	// do an initial check shortly after start
	_ = pp.checkSubscriptionStatus()
	for {
		select {
		case <-pp.ctx.Done():
			return
		case <-t.C:
			if err := pp.checkSubscriptionStatus(); err != nil {
				log.W.F("subscription status check failed: %v", err)
			}
		}
	}
}

// syncFollowList builds a kind-3 event from the relay identity containing only active subscribers
func (pp *PaymentProcessor) syncFollowList() error {
	// ensure we have a relay identity secret
	skb, err := pp.db.GetRelayIdentitySecret()
	if err != nil || len(skb) != 32 {
		return nil // nothing to do if no identity
	}
	// collect active subscribers
	actives, err := pp.getActiveSubscriberPubkeys()
	if err != nil {
		return err
	}
	// signer
	sign := p8k.MustNew()
	if err := sign.InitSec(skb); err != nil {
		return err
	}
	// build follow list event
	ev := event.New()
	ev.Kind = kind.FollowList.K
	ev.Pubkey = sign.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Tags = tag.NewS()
	for _, pk := range actives {
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("p", hex.Enc(pk)))
	}
	// sign and save
	ev.Sign(sign)
	if _, err := pp.db.SaveEvent(pp.ctx, ev); err != nil {
		return err
	}
	log.I.F(
		"updated relay follow list with %d active subscribers", len(actives),
	)
	return nil
}

// getActiveSubscriberPubkeys scans the subscription records and returns active ones
func (pp *PaymentProcessor) getActiveSubscriberPubkeys() ([][]byte, error) {
	prefix := []byte("sub:")
	now := time.Now()
	var out [][]byte
	err := pp.db.DB.View(
		func(txn *badger.Txn) error {
			it := txn.NewIterator(badger.DefaultIteratorOptions)
			defer it.Close()
			for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
				item := it.Item()
				key := item.KeyCopy(nil)
				// key format: sub:<hexpub>
				hexpub := string(key[len(prefix):])
				var sub database.Subscription
				if err := item.Value(
					func(val []byte) error {
						return json.Unmarshal(val, &sub)
					},
				); err != nil {
					return err
				}
				if now.Before(sub.TrialEnd) || (!sub.PaidUntil.IsZero() && now.Before(sub.PaidUntil)) {
					if b, err := hex.Dec(hexpub); err == nil {
						out = append(out, b)
					}
				}
			}
			return nil
		},
	)
	return out, err
}

// checkSubscriptionStatus scans all subscriptions and creates warning/reminder notes
func (pp *PaymentProcessor) checkSubscriptionStatus() error {
	prefix := []byte("sub:")
	now := time.Now()
	sevenDaysFromNow := now.AddDate(0, 0, 7)

	return pp.db.DB.View(
		func(txn *badger.Txn) error {
			it := txn.NewIterator(badger.DefaultIteratorOptions)
			defer it.Close()
			for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
				item := it.Item()
				key := item.KeyCopy(nil)
				// key format: sub:<hexpub>
				hexpub := string(key[len(prefix):])

				var sub database.Subscription
				if err := item.Value(
					func(val []byte) error {
						return json.Unmarshal(val, &sub)
					},
				); err != nil {
					continue // skip invalid subscription records
				}

				pubkey, err := hex.Dec(hexpub)
				if err != nil {
					continue // skip invalid pubkey
				}

				// Check if paid subscription is expiring in 7 days
				if !sub.PaidUntil.IsZero() {
					// Format dates for comparison (ignore time component)
					paidUntilDate := sub.PaidUntil.Truncate(24 * time.Hour)
					sevenDaysDate := sevenDaysFromNow.Truncate(24 * time.Hour)

					if paidUntilDate.Equal(sevenDaysDate) {
						go pp.createExpiryWarningNote(pubkey, sub.PaidUntil)
					}
				}

				// Check if user is on trial (no paid subscription, trial not expired)
				if sub.PaidUntil.IsZero() && now.Before(sub.TrialEnd) {
					go pp.createTrialReminderNote(pubkey, sub.TrialEnd)
				}
			}
			return nil
		},
	)
}

// createExpiryWarningNote creates a warning note for users whose paid subscription expires in 7 days
func (pp *PaymentProcessor) createExpiryWarningNote(
	userPubkey []byte, expiryTime time.Time,
) error {
	// Get relay identity secret to sign the note
	skb, err := pp.db.GetRelayIdentitySecret()
	if err != nil || len(skb) != 32 {
		return fmt.Errorf("no relay identity configured")
	}

	// Initialize signer
	sign := p8k.MustNew()
	if err := sign.InitSec(skb); err != nil {
		return fmt.Errorf("failed to initialize signer: %w", err)
	}

	monthlyPrice := pp.config.MonthlyPriceSats
	if monthlyPrice <= 0 {
		monthlyPrice = 6000
	}

	// Get relay npub for content link
	relayNpubForContent, err := bech32encoding.BinToNpub(sign.Pub())
	if err != nil {
		return fmt.Errorf("failed to encode relay npub: %w", err)
	}

	// Create the warning note content
	content := fmt.Sprintf(
		`⚠️ Subscription Expiring Soon ⚠️

Your paid subscription to this relay will expire in 7 days on %s.

💰 To extend your subscription:
- Monthly price: %d sats
- Zap this note with your payment amount
- Each %d sats = 30 days of access

⚡ Payment Instructions:
1. Use any Lightning wallet that supports zaps
2. Zap this note with your payment
3. Your subscription will be automatically extended

Don't lose access to your private relay! Extend your subscription today.

Relay: nostr:%s

Log in to the relay dashboard to access your configuration at: %s`,
		expiryTime.Format("2006-01-02 15:04:05 UTC"), monthlyPrice,
		monthlyPrice, string(relayNpubForContent), pp.getDashboardURL(),
	)

	// Build the event
	ev := event.New()
	ev.Kind = kind.TextNote.K // Kind 1 for text note
	ev.Pubkey = sign.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Content = []byte(content)
	ev.Tags = tag.NewS()

	// Add "p" tag for the user
	*ev.Tags = append(*ev.Tags, tag.NewFromAny("p", hex.Enc(userPubkey)))

	// Add expiration tag (5 days from creation)
	noteExpiry := time.Now().AddDate(0, 0, 5)
	*ev.Tags = append(
		*ev.Tags,
		tag.NewFromAny("expiration", fmt.Sprintf("%d", noteExpiry.Unix())),
	)

	// Add "private" tag with authorized npubs (user and relay)
	var authorizedNpubs []string

	// Add user npub
	userNpub, err := bech32encoding.BinToNpub(userPubkey)
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(userNpub))
	}

	// Add relay npub
	relayNpub, err := bech32encoding.BinToNpub(sign.Pub())
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(relayNpub))
	}

	// Create the private tag with comma-separated npubs
	if len(authorizedNpubs) > 0 {
		privateTagValue := strings.Join(authorizedNpubs, ",")
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("private", privateTagValue))
		// Add protected "-" tag to mark this event as protected
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("-", ""))
	}

	// Add a special tag to mark this as an expiry warning
	*ev.Tags = append(
		*ev.Tags, tag.NewFromAny("warning", "subscription-expiry"),
	)

	// Sign and save the event
	ev.Sign(sign)
	if _, err := pp.db.SaveEvent(pp.ctx, ev); err != nil {
		return fmt.Errorf("failed to save expiry warning note: %w", err)
	}

	log.I.F(
		"created expiry warning note for user %s (expires %s)",
		hex.Enc(userPubkey), expiryTime.Format("2006-01-02"),
	)
	return nil
}

// createTrialReminderNote creates a reminder note for users on trial to support the relay
func (pp *PaymentProcessor) createTrialReminderNote(
	userPubkey []byte, trialEnd time.Time,
) error {
	// Get relay identity secret to sign the note
	skb, err := pp.db.GetRelayIdentitySecret()
	if err != nil || len(skb) != 32 {
		return fmt.Errorf("no relay identity configured")
	}

	// Initialize signer
	sign := p8k.MustNew()
	if err := sign.InitSec(skb); err != nil {
		return fmt.Errorf("failed to initialize signer: %w", err)
	}

	monthlyPrice := pp.config.MonthlyPriceSats
	if monthlyPrice <= 0 {
		monthlyPrice = 6000
	}

	// Calculate daily rate
	dailyRate := monthlyPrice / 30

	// Get relay npub for content link
	relayNpubForContent, err := bech32encoding.BinToNpub(sign.Pub())
	if err != nil {
		return fmt.Errorf("failed to encode relay npub: %w", err)
	}

	// Create the reminder note content
	content := fmt.Sprintf(
		`🆓 Free Trial Reminder 🆓

You're currently using this relay for FREE! Your trial expires on %s.

🙏 Support Relay Operations:
This relay provides you with private, censorship-resistant communication. Please consider supporting its continued operation.

💰 Subscription Details:
- Monthly price: %d sats (%d sats/day)
- Fair pricing for premium service
- Helps keep the relay running 24/7

⚡ How to Subscribe:
Simply zap this note with your payment amount:
- Each %d sats = 30 days of access
- Payment is processed automatically
- No account setup required

Thank you for considering supporting decentralized communication!

Relay: nostr:%s

Log in to the relay dashboard to access your configuration at: %s`,
		trialEnd.Format("2006-01-02 15:04:05 UTC"), monthlyPrice, dailyRate,
		monthlyPrice, string(relayNpubForContent), pp.getDashboardURL(),
	)

	// Build the event
	ev := event.New()
	ev.Kind = kind.TextNote.K // Kind 1 for text note
	ev.Pubkey = sign.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Content = []byte(content)
	ev.Tags = tag.NewS()

	// Add "p" tag for the user
	*ev.Tags = append(*ev.Tags, tag.NewFromAny("p", hex.Enc(userPubkey)))

	// Add expiration tag (5 days from creation)
	noteExpiry := time.Now().AddDate(0, 0, 5)
	*ev.Tags = append(
		*ev.Tags,
		tag.NewFromAny("expiration", fmt.Sprintf("%d", noteExpiry.Unix())),
	)

	// Add "private" tag with authorized npubs (user and relay)
	var authorizedNpubs []string

	// Add user npub
	userNpub, err := bech32encoding.BinToNpub(userPubkey)
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(userNpub))
	}

	// Add relay npub
	relayNpub, err := bech32encoding.BinToNpub(sign.Pub())
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(relayNpub))
	}

	// Create the private tag with comma-separated npubs
	if len(authorizedNpubs) > 0 {
		privateTagValue := strings.Join(authorizedNpubs, ",")
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("private", privateTagValue))
		// Add protected "-" tag to mark this event as protected
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("-", ""))
	}

	// Add a special tag to mark this as a trial reminder
	*ev.Tags = append(*ev.Tags, tag.NewFromAny("reminder", "trial-support"))

	// Sign and save the event
	ev.Sign(sign)
	if _, err := pp.db.SaveEvent(pp.ctx, ev); err != nil {
		return fmt.Errorf("failed to save trial reminder note: %w", err)
	}

	log.I.F(
		"created trial reminder note for user %s (trial ends %s)",
		hex.Enc(userPubkey), trialEnd.Format("2006-01-02"),
	)
	return nil
}

// handleNotification processes incoming payment notifications
func (pp *PaymentProcessor) handleNotification(
	notificationType string, notification map[string]any,
) error {
	// Only process payment_received notifications
	if notificationType != "payment_received" {
		return nil
	}

	amount, ok := notification["amount"].(float64)
	if !ok {
		return fmt.Errorf("invalid amount")
	}

	// Prefer explicit payer/relay pubkeys if provided in metadata
	var payerPubkey []byte
	var userNpub string
	var metadata map[string]any
	if md, ok := notification["metadata"].(map[string]any); ok {
		metadata = md
		if s, ok := metadata["payer_pubkey"].(string); ok && s != "" {
			if pk, err := decodeAnyPubkey(s); err == nil {
				payerPubkey = pk
			}
		}
		if payerPubkey == nil {
			if s, ok := metadata["sender_pubkey"].(string); ok && s != "" { // alias
				if pk, err := decodeAnyPubkey(s); err == nil {
					payerPubkey = pk
				}
			}
		}
		// Optional: the intended subscriber npub (for backwards compat)
		if userNpub == "" {
			if npubField, ok := metadata["npub"].(string); ok {
				userNpub = npubField
			}
		}
		// If relay identity pubkey is provided, verify it matches ours
		if s, ok := metadata["relay_pubkey"].(string); ok && s != "" {
			if rpk, err := decodeAnyPubkey(s); err == nil {
				if skb, err := pp.db.GetRelayIdentitySecret(); err == nil && len(skb) == 32 {
					signer := p8k.MustNew()
					if err := signer.InitSec(skb); err == nil {
						if !strings.EqualFold(
							hex.Enc(rpk), hex.Enc(signer.Pub()),
						) {
							log.W.F(
								"relay_pubkey in payment metadata does not match this relay identity: got %s want %s",
								hex.Enc(rpk), hex.Enc(signer.Pub()),
							)
						}
					}
				}
			}
		}
	}

	// Fallback: extract npub from description or metadata
	description, _ := notification["description"].(string)
	if userNpub == "" {
		userNpub = pp.extractNpubFromDescription(description)
	}

	var pubkey []byte
	var err error
	if payerPubkey != nil {
		pubkey = payerPubkey
	} else {
		if userNpub == "" {
			return fmt.Errorf("no payer_pubkey or npub provided in payment notification")
		}
		pubkey, err = pp.npubToPubkey(userNpub)
		if err != nil {
			return fmt.Errorf("invalid npub: %w", err)
		}
	}

	satsReceived := int64(amount / 1000)
	
	// Parse zap memo for blossom service level
	blossomLevel := pp.parseBlossomServiceLevel(description, metadata)
	
	// Calculate subscription days (for relay access)
	monthlyPrice := pp.config.MonthlyPriceSats
	if monthlyPrice <= 0 {
		monthlyPrice = 6000
	}

	days := int((float64(satsReceived) / float64(monthlyPrice)) * 30)
	if days < 1 {
		return fmt.Errorf("payment amount too small")
	}

	// Extend relay subscription
	if err := pp.db.ExtendSubscription(pubkey, days); err != nil {
		return fmt.Errorf("failed to extend subscription: %w", err)
	}

	// If blossom service level specified, extend blossom subscription
	if blossomLevel != "" {
		if err := pp.extendBlossomSubscription(pubkey, satsReceived, blossomLevel, days); err != nil {
			log.W.F("failed to extend blossom subscription: %v", err)
			// Don't fail the payment if blossom subscription fails
		}
	}

	// Record payment history
	invoice, _ := notification["invoice"].(string)
	preimage, _ := notification["preimage"].(string)
	if err := pp.db.RecordPayment(
		pubkey, satsReceived, invoice, preimage,
	); err != nil {
		log.E.F("failed to record payment: %v", err)
	}

	// Log helpful identifiers
	var payerHex = hex.Enc(pubkey)
	if userNpub == "" {
		log.I.F(
			"payment processed: payer %s %d sats -> %d days", payerHex,
			satsReceived, days,
		)
	} else {
		log.I.F(
			"payment processed: %s (%s) %d sats -> %d days", userNpub, payerHex,
			satsReceived, days,
		)
	}

	// Update ACL follows cache and relay follow list immediately
	if pp.config != nil && pp.config.ACLMode == "follows" {
		acl.Registry.AddFollow(pubkey)
	}
	// Trigger an immediate follow-list sync in background (best-effort)
	go func() { _ = pp.syncFollowList() }()

	// Create a note with payment confirmation and private tag
	if err := pp.createPaymentNote(pubkey, satsReceived, days); err != nil {
		log.E.F("failed to create payment note: %v", err)
	}

	return nil
}

// createPaymentNote creates a note recording the payment with private tag for authorization
func (pp *PaymentProcessor) createPaymentNote(
	payerPubkey []byte, satsReceived int64, days int,
) error {
	// Get relay identity secret to sign the note
	skb, err := pp.db.GetRelayIdentitySecret()
	if err != nil || len(skb) != 32 {
		return fmt.Errorf("no relay identity configured")
	}

	// Initialize signer
	sign := p8k.MustNew()
	if err := sign.InitSec(skb); err != nil {
		return fmt.Errorf("failed to initialize signer: %w", err)
	}

	// Get subscription info to determine expiry
	sub, err := pp.db.GetSubscription(payerPubkey)
	if err != nil {
		return fmt.Errorf("failed to get subscription: %w", err)
	}

	var expiryTime time.Time
	if sub != nil && !sub.PaidUntil.IsZero() {
		expiryTime = sub.PaidUntil
	} else {
		expiryTime = time.Now().AddDate(0, 0, days)
	}

	// Get relay npub for content link
	relayNpubForContent, err := bech32encoding.BinToNpub(sign.Pub())
	if err != nil {
		return fmt.Errorf("failed to encode relay npub: %w", err)
	}

	// Create the note content with nostr:npub link and dashboard link
	content := fmt.Sprintf(
		"Payment received: %d sats for %d days. Subscription expires: %s\n\nRelay: nostr:%s\n\nLog in to the relay dashboard to access your configuration at: %s",
		satsReceived, days, expiryTime.Format("2006-01-02 15:04:05 UTC"),
		string(relayNpubForContent), pp.getDashboardURL(),
	)

	// Build the event
	ev := event.New()
	ev.Kind = kind.TextNote.K // Kind 1 for text note
	ev.Pubkey = sign.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Content = []byte(content)
	ev.Tags = tag.NewS()

	// Add "p" tag for the payer
	*ev.Tags = append(*ev.Tags, tag.NewFromAny("p", hex.Enc(payerPubkey)))

	// Add expiration tag (5 days from creation)
	noteExpiry := time.Now().AddDate(0, 0, 5)
	*ev.Tags = append(
		*ev.Tags,
		tag.NewFromAny("expiration", fmt.Sprintf("%d", noteExpiry.Unix())),
	)

	// Add "private" tag with authorized npubs (payer and relay)
	var authorizedNpubs []string

	// Add payer npub
	payerNpub, err := bech32encoding.BinToNpub(payerPubkey)
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(payerNpub))
	}

	// Add relay npub
	relayNpub, err := bech32encoding.BinToNpub(sign.Pub())
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(relayNpub))
	}

	// Create the private tag with comma-separated npubs
	if len(authorizedNpubs) > 0 {
		privateTagValue := strings.Join(authorizedNpubs, ",")
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("private", privateTagValue))
		// Add protected "-" tag to mark this event as protected
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("-", ""))
	}

	// Sign and save the event
	ev.Sign(sign)
	if _, err := pp.db.SaveEvent(pp.ctx, ev); err != nil {
		return fmt.Errorf("failed to save payment note: %w", err)
	}

	log.I.F(
		"created payment note for %s with private authorization",
		hex.Enc(payerPubkey),
	)
	return nil
}

// CreateWelcomeNote creates a welcome note for first-time users with private tag for authorization
func (pp *PaymentProcessor) CreateWelcomeNote(userPubkey []byte) error {
	// Get relay identity secret to sign the note
	skb, err := pp.db.GetRelayIdentitySecret()
	if err != nil || len(skb) != 32 {
		return fmt.Errorf("no relay identity configured")
	}

	// Initialize signer
	sign := p8k.MustNew()
	if err := sign.InitSec(skb); err != nil {
		return fmt.Errorf("failed to initialize signer: %w", err)
	}

	monthlyPrice := pp.config.MonthlyPriceSats
	if monthlyPrice <= 0 {
		monthlyPrice = 6000
	}

	// Get relay npub for content link
	relayNpubForContent, err := bech32encoding.BinToNpub(sign.Pub())
	if err != nil {
		return fmt.Errorf("failed to encode relay npub: %w", err)
	}

	// Get user npub for personalized greeting
	userNpub, err := bech32encoding.BinToNpub(userPubkey)
	if err != nil {
		return fmt.Errorf("failed to encode user npub: %w", err)
	}

	// Create the welcome note content with privacy notice and personalized greeting
	content := fmt.Sprintf(
		`This note is only visible to you

Hi nostr:%s

Welcome to the relay! 🎉

You have a FREE 30-day trial that started when you first logged in.

💰 Subscription Details:
- Monthly price: %d sats
- Trial period: 30 days from first login

💡 How to Subscribe:
To extend your subscription after the trial ends, simply zap this note with the amount you want to pay. Each %d sats = 30 days of access.

⚡ Payment Instructions:
1. Use any Lightning wallet that supports zaps
2. Zap this note with your payment
3. Your subscription will be automatically extended

Relay: nostr:%s

Log in to the relay dashboard to access your configuration at: %s

Enjoy your time on the relay!`, string(userNpub), monthlyPrice, monthlyPrice,
		string(relayNpubForContent), pp.getDashboardURL(),
	)

	// Build the event
	ev := event.New()
	ev.Kind = kind.TextNote.K // Kind 1 for text note
	ev.Pubkey = sign.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Content = []byte(content)
	ev.Tags = tag.NewS()

	// Add "p" tag for the user with mention in third field
	*ev.Tags = append(*ev.Tags, tag.NewFromAny("p", hex.Enc(userPubkey), "", "mention"))

	// Add expiration tag (5 days from creation)
	noteExpiry := time.Now().AddDate(0, 0, 5)
	*ev.Tags = append(
		*ev.Tags,
		tag.NewFromAny("expiration", fmt.Sprintf("%d", noteExpiry.Unix())),
	)

	// Add "private" tag with authorized npubs (user and relay)
	var authorizedNpubs []string

	// Add user npub (already encoded above)
	authorizedNpubs = append(authorizedNpubs, string(userNpub))

	// Add relay npub
	relayNpub, err := bech32encoding.BinToNpub(sign.Pub())
	if err == nil {
		authorizedNpubs = append(authorizedNpubs, string(relayNpub))
	}

	// Create the private tag with comma-separated npubs
	if len(authorizedNpubs) > 0 {
		privateTagValue := strings.Join(authorizedNpubs, ",")
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("private", privateTagValue))
		// Add protected "-" tag to mark this event as protected
		*ev.Tags = append(*ev.Tags, tag.NewFromAny("-", ""))
	}

	// Add a special tag to mark this as a welcome note
	*ev.Tags = append(*ev.Tags, tag.NewFromAny("welcome", "first-time-user"))

	// Sign and save the event
	ev.Sign(sign)
	if _, err := pp.db.SaveEvent(pp.ctx, ev); err != nil {
		return fmt.Errorf("failed to save welcome note: %w", err)
	}

	log.I.F("created welcome note for first-time user %s", hex.Enc(userPubkey))
	return nil
}

// SetDashboardURL sets the dynamic dashboard URL based on HTTP request
func (pp *PaymentProcessor) SetDashboardURL(url string) {
	pp.dashboardURL = url
}

// getDashboardURL returns the dashboard URL for the relay
func (pp *PaymentProcessor) getDashboardURL() string {
	// Use dynamic URL if available
	if pp.dashboardURL != "" {
		return pp.dashboardURL
	}
	// Fallback to static config
	if pp.config.RelayURL != "" {
		return pp.config.RelayURL
	}
	// Default fallback if no URL is configured
	return "https://your-relay.example.com"
}

// extractNpubFromDescription extracts an npub from the payment description
func (pp *PaymentProcessor) extractNpubFromDescription(description string) string {
	// check if the entire description is just an npub
	description = strings.TrimSpace(description)
	if strings.HasPrefix(description, "npub1") && len(description) == 63 {
		return description
	}

	// Look for npub1... pattern in the description
	parts := strings.Fields(description)
	for _, part := range parts {
		if strings.HasPrefix(part, "npub1") && len(part) == 63 {
			return part
		}
	}

	return ""
}

// npubToPubkey converts an npub string to pubkey bytes
func (pp *PaymentProcessor) npubToPubkey(npubStr string) ([]byte, error) {
	// Validate npub format
	if !strings.HasPrefix(npubStr, "npub1") || len(npubStr) != 63 {
		return nil, fmt.Errorf("invalid npub format")
	}

	// Decode using bech32encoding
	prefix, value, err := bech32encoding.Decode([]byte(npubStr))
	if err != nil {
		return nil, fmt.Errorf("failed to decode npub: %w", err)
	}

	if !strings.EqualFold(string(prefix), "npub") {
		return nil, fmt.Errorf("invalid prefix: %s", string(prefix))
	}

	pubkey, ok := value.([]byte)
	if !ok {
		return nil, fmt.Errorf("decoded value is not []byte")
	}

	return pubkey, nil
}

// parseBlossomServiceLevel parses the zap memo for a blossom service level specification
// Format: "blossom:level" or "blossom:level:storage_mb" in description or metadata memo field
func (pp *PaymentProcessor) parseBlossomServiceLevel(
	description string, metadata map[string]any,
) string {
	// Check metadata memo field first
	if metadata != nil {
		if memo, ok := metadata["memo"].(string); ok && memo != "" {
			if level := pp.extractBlossomLevelFromMemo(memo); level != "" {
				return level
			}
		}
	}

	// Check description
	if description != "" {
		if level := pp.extractBlossomLevelFromMemo(description); level != "" {
			return level
		}
	}

	return ""
}

// extractBlossomLevelFromMemo extracts blossom service level from memo text
// Supports formats: "blossom:basic", "blossom:premium", "blossom:basic:100"
func (pp *PaymentProcessor) extractBlossomLevelFromMemo(memo string) string {
	// Look for "blossom:" prefix
	parts := strings.Fields(memo)
	for _, part := range parts {
		if strings.HasPrefix(part, "blossom:") {
			// Extract level name (e.g., "basic", "premium")
			levelPart := strings.TrimPrefix(part, "blossom:")
			// Remove any storage specification (e.g., ":100")
			if colonIdx := strings.Index(levelPart, ":"); colonIdx > 0 {
				levelPart = levelPart[:colonIdx]
			}
			// Validate level exists in config
			if pp.isValidBlossomLevel(levelPart) {
				return levelPart
			}
		}
	}
	return ""
}

// isValidBlossomLevel checks if a service level is configured
func (pp *PaymentProcessor) isValidBlossomLevel(level string) bool {
	if pp.config == nil || pp.config.BlossomServiceLevels == "" {
		return false
	}

	// Parse service levels from config
	levels := strings.Split(pp.config.BlossomServiceLevels, ",")
	for _, l := range levels {
		l = strings.TrimSpace(l)
		if strings.HasPrefix(l, level+":") {
			return true
		}
	}
	return false
}

// parseServiceLevelStorage parses storage quota in MB per sat per month for a service level
func (pp *PaymentProcessor) parseServiceLevelStorage(level string) (int64, error) {
	if pp.config == nil || pp.config.BlossomServiceLevels == "" {
		return 0, fmt.Errorf("blossom service levels not configured")
	}

	levels := strings.Split(pp.config.BlossomServiceLevels, ",")
	for _, l := range levels {
		l = strings.TrimSpace(l)
		if strings.HasPrefix(l, level+":") {
			parts := strings.Split(l, ":")
			if len(parts) >= 2 {
				var storageMB float64
				if _, err := fmt.Sscanf(parts[1], "%f", &storageMB); err != nil {
					return 0, fmt.Errorf("invalid storage format: %w", err)
				}
				return int64(storageMB), nil
			}
		}
	}
	return 0, fmt.Errorf("service level %s not found", level)
}

// extendBlossomSubscription extends or creates a blossom subscription with service level
func (pp *PaymentProcessor) extendBlossomSubscription(
	pubkey []byte, satsReceived int64, level string, days int,
) error {
	// Get storage quota per sat per month for this level
	storageMBPerSatPerMonth, err := pp.parseServiceLevelStorage(level)
	if err != nil {
		return fmt.Errorf("failed to parse service level storage: %w", err)
	}

	// Calculate storage quota: sats * storage_mb_per_sat_per_month * (days / 30)
	storageMB := int64(float64(satsReceived) * float64(storageMBPerSatPerMonth) * (float64(days) / 30.0))

	// Extend blossom subscription
	if err := pp.db.ExtendBlossomSubscription(pubkey, level, storageMB, days); err != nil {
		return fmt.Errorf("failed to extend blossom subscription: %w", err)
	}

	log.I.F(
		"extended blossom subscription: level=%s, storage=%d MB, days=%d",
		level, storageMB, days,
	)

	return nil
}

// UpdateRelayProfile creates or updates the relay's kind 0 profile with subscription information
func (pp *PaymentProcessor) UpdateRelayProfile() error {
	// Get relay identity secret to sign the profile
	skb, err := pp.db.GetRelayIdentitySecret()
	if err != nil || len(skb) != 32 {
		return fmt.Errorf("no relay identity configured")
	}

	// Initialize signer
	sign := p8k.MustNew()
	if err := sign.InitSec(skb); err != nil {
		return fmt.Errorf("failed to initialize signer: %w", err)
	}

	monthlyPrice := pp.config.MonthlyPriceSats
	if monthlyPrice <= 0 {
		monthlyPrice = 6000
	}

	// Calculate daily rate
	dailyRate := monthlyPrice / 30

	// Get relay wss:// URL - use dashboard URL but with wss:// scheme
	relayURL := strings.Replace(pp.getDashboardURL(), "https://", "wss://", 1)

	// Create profile content as JSON
	profileContent := fmt.Sprintf(
		`{
	"name": "Relay Bot",
	"about": "This relay requires a subscription to access. Zap any of my notes to pay for access. Monthly price: %d sats (%d sats/day). Relay: %s",
	"lud16": "",
	"nip05": "",
	"website": "%s"
}`, monthlyPrice, dailyRate, relayURL, pp.getDashboardURL(),
	)

	// Build the profile event
	ev := event.New()
	ev.Kind = kind.ProfileMetadata.K // Kind 0 for profile metadata
	ev.Pubkey = sign.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Content = []byte(profileContent)
	ev.Tags = tag.NewS()

	// Sign and save the event
	ev.Sign(sign)
	if _, err := pp.db.SaveEvent(pp.ctx, ev); err != nil {
		return fmt.Errorf("failed to save relay profile: %w", err)
	}

	log.I.F("updated relay profile with subscription information")
	return nil
}

// decodeAnyPubkey decodes a public key from either hex string or npub format
func decodeAnyPubkey(s string) ([]byte, error) {
	s = strings.TrimSpace(s)
	if strings.HasPrefix(s, "npub1") {
		prefix, value, err := bech32encoding.Decode([]byte(s))
		if err != nil {
			return nil, fmt.Errorf("failed to decode npub: %w", err)
		}
		if !strings.EqualFold(string(prefix), "npub") {
			return nil, fmt.Errorf("invalid prefix: %s", string(prefix))
		}
		b, ok := value.([]byte)
		if !ok {
			return nil, fmt.Errorf("decoded value is not []byte")
		}
		return b, nil
	}
	// assume hex-encoded public key
	return hex.Dec(s)
}
