package profile

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"sync"
	"time"

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

// ProfileMetadata represents user profile information
type ProfileMetadata struct {
	Name        string `json:"name"`
	DisplayName string `json:"display_name"`
	About       string `json:"about"`
	Picture     string `json:"picture"`
	Banner      string `json:"banner"`
	Website     string `json:"website"`
	Nip05       string `json:"nip05"`
	LUD16       string `json:"lud16"`
}

// RelaySet represents a user's relay configuration (NIP-65)
type RelaySet struct {
	Read  []string `json:"read"`
	Write []string `json:"write"`
}

// ProfileFetcher handles fetching user profiles from their relay sets
type ProfileFetcher struct {
	defaultRelays []string
	cache         map[string]*CachedProfile
	cacheMutex    sync.RWMutex
	cacheTimeout  time.Duration
}

// CachedProfile represents a cached user profile
type CachedProfile struct {
	Profile   *ProfileMetadata
	RelaySet  *RelaySet
	FetchedAt time.Time
}

// NewProfileFetcher creates a new profile fetcher
func NewProfileFetcher(defaultRelays []string) *ProfileFetcher {
	return &ProfileFetcher{
		defaultRelays: defaultRelays,
		cache:         make(map[string]*CachedProfile),
		cacheTimeout:  30 * time.Minute, // Cache profiles for 30 minutes
	}
}

// GetUserProfile fetches a user's profile metadata using their relay set
func (pf *ProfileFetcher) GetUserProfile(pubkeyHex string) (*ProfileMetadata, error) {
	// Check cache first
	pf.cacheMutex.RLock()
	if cached, exists := pf.cache[pubkeyHex]; exists {
		if time.Since(cached.FetchedAt) < pf.cacheTimeout {
			pf.cacheMutex.RUnlock()
			return cached.Profile, nil
		}
	}
	pf.cacheMutex.RUnlock()

	// Fetch relay set first (NIP-65)
	relaySet, err := pf.fetchRelaySet(pubkeyHex)
	if err != nil {
		log.Printf("Failed to fetch relay set for %s: %v", pubkeyHex[:8], err)
		relaySet = &RelaySet{
			Read:  pf.defaultRelays,
			Write: pf.defaultRelays,
		}
	}

	// Fetch profile from relay set
	profile, err := pf.fetchProfileFromRelays(pubkeyHex, relaySet.Read)
	if err != nil {
		log.Printf("Failed to fetch profile for %s: %v", pubkeyHex[:8], err)
		return nil, err
	}

	// Cache the result
	pf.cacheMutex.Lock()
	pf.cache[pubkeyHex] = &CachedProfile{
		Profile:   profile,
		RelaySet:  relaySet,
		FetchedAt: time.Now(),
	}
	pf.cacheMutex.Unlock()

	return profile, nil
}

// fetchRelaySet discovers a user's relay set using NIP-65
func (pf *ProfileFetcher) fetchRelaySet(pubkeyHex string) (*RelaySet, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Try to fetch relay list from default relays
	for _, relayURL := range pf.defaultRelays {
		relay, err := nostr.RelayConnect(ctx, relayURL)
		if err != nil {
			continue
		}

		// Request relay list event (kind 10002 - NIP-65)
		filter := nostr.Filter{
			Authors: []string{pubkeyHex},
			Kinds:   []int{10002}, // NIP-65 relay list
			Limit:   1,
		}

		sub, err := relay.Subscribe(ctx, []nostr.Filter{filter})
		if err != nil {
			relay.Close()
			continue
		}

		select {
		case event := <-sub.Events:
			relay.Close()
			return pf.parseRelaySet(event), nil
		case <-time.After(5 * time.Second):
			relay.Close()
			continue
		}
	}

	return nil, fmt.Errorf("no relay set found")
}

// parseRelaySet parses NIP-65 relay list event
func (pf *ProfileFetcher) parseRelaySet(event *nostr.Event) *RelaySet {
	relaySet := &RelaySet{
		Read:  []string{},
		Write: []string{},
	}

	for _, tag := range event.Tags {
		if len(tag) >= 2 && tag[0] == "r" {
			relayURL := tag[1]
			
			// Default to read+write if no marker specified
			if len(tag) == 2 {
				relaySet.Read = append(relaySet.Read, relayURL)
				relaySet.Write = append(relaySet.Write, relayURL)
			} else if len(tag) >= 3 {
				marker := tag[2]
				if marker == "read" || marker == "" {
					relaySet.Read = append(relaySet.Read, relayURL)
				}
				if marker == "write" || marker == "" {
					relaySet.Write = append(relaySet.Write, relayURL)
				}
			}
		}
	}

	// If no relays found, use defaults
	if len(relaySet.Read) == 0 {
		relaySet.Read = pf.defaultRelays
	}
	if len(relaySet.Write) == 0 {
		relaySet.Write = pf.defaultRelays
	}

	return relaySet
}

// fetchProfileFromRelays fetches user profile (kind 0) from their relay set
func (pf *ProfileFetcher) fetchProfileFromRelays(pubkeyHex string, relays []string) (*ProfileMetadata, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	// Try each relay until we get a profile
	for _, relayURL := range relays {
		relay, err := nostr.RelayConnect(ctx, relayURL)
		if err != nil {
			continue
		}

		// Request profile event (kind 0)
		filter := nostr.Filter{
			Authors: []string{pubkeyHex},
			Kinds:   []int{0}, // Profile metadata
			Limit:   1,
		}

		sub, err := relay.Subscribe(ctx, []nostr.Filter{filter})
		if err != nil {
			relay.Close()
			continue
		}

		select {
		case event := <-sub.Events:
			relay.Close()
			return pf.parseProfile(event), nil
		case <-time.After(5 * time.Second):
			relay.Close()
			continue
		}
	}

	return nil, fmt.Errorf("no profile found")
}

// parseProfile parses a kind 0 profile event
func (pf *ProfileFetcher) parseProfile(event *nostr.Event) *ProfileMetadata {
	var profile ProfileMetadata
	
	if err := json.Unmarshal([]byte(event.Content), &profile); err != nil {
		log.Printf("Failed to parse profile content: %v", err)
		return &ProfileMetadata{
			Name: fmt.Sprintf("User %s", event.PubKey[:8]),
		}
	}

	// Set fallback name if empty
	if profile.Name == "" && profile.DisplayName == "" {
		profile.Name = fmt.Sprintf("User %s", event.PubKey[:8])
	}

	return &profile
}

// GetBatchProfiles fetches profiles for multiple users efficiently
func (pf *ProfileFetcher) GetBatchProfiles(pubkeyHexList []string) map[string]*ProfileMetadata {
	results := make(map[string]*ProfileMetadata)
	var wg sync.WaitGroup
	resultMutex := sync.Mutex{}

	// Limit concurrent requests
	semaphore := make(chan struct{}, 5)

	for _, pubkey := range pubkeyHexList {
		wg.Add(1)
		go func(pk string) {
			defer wg.Done()
			semaphore <- struct{}{}
			defer func() { <-semaphore }()

			profile, err := pf.GetUserProfile(pk)
			if err == nil && profile != nil {
				resultMutex.Lock()
				results[pk] = profile
				resultMutex.Unlock()
			}
		}(pubkey)
	}

	wg.Wait()
	return results
}

// GetDisplayName returns the best display name for a user
func (pf *ProfileFetcher) GetDisplayName(pubkeyHex string) string {
	profile, err := pf.GetUserProfile(pubkeyHex)
	if err != nil || profile == nil {
		return pubkeyHex[:8] + "..."
	}

	if profile.DisplayName != "" {
		return profile.DisplayName
	}
	if profile.Name != "" {
		return profile.Name
	}
	
	return pubkeyHex[:8] + "..."
}