package directory_client

import (
	"sync"

	"lol.mleku.dev/errorf"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"next.orly.dev/pkg/protocol/directory"
)

// IdentityResolver manages identity resolution and key delegation tracking.
//
// It maintains mappings between delegate keys and their primary identities,
// enabling clients to resolve the actual identity behind any signing key.
type IdentityResolver struct {
	mu sync.RWMutex

	// delegateToIdentity maps delegate public keys to their primary identity
	delegateToIdentity map[string]string

	// identityToDelegates maps primary identities to their delegate keys
	identityToDelegates map[string]map[string]bool

	// identityTagCache stores full identity tags by delegate key
	identityTagCache map[string]*directory.IdentityTag

	// publicKeyAds stores public key advertisements by key ID
	publicKeyAds map[string]*directory.PublicKeyAdvertisement
}

// NewIdentityResolver creates a new identity resolver instance.
func NewIdentityResolver() *IdentityResolver {
	return &IdentityResolver{
		delegateToIdentity:  make(map[string]string),
		identityToDelegates: make(map[string]map[string]bool),
		identityTagCache:    make(map[string]*directory.IdentityTag),
		publicKeyAds:        make(map[string]*directory.PublicKeyAdvertisement),
	}
}

// ProcessEvent processes an event to extract and cache identity information.
//
// This should be called for all directory events to keep the resolver's
// internal state up to date.
func (r *IdentityResolver) ProcessEvent(ev *event.E) {
	if ev == nil {
		return
	}

	// Try to parse identity tag (I tag)
	identityTag := extractIdentityTag(ev)
	if identityTag != nil {
		r.cacheIdentityTag(identityTag)
	}

	// Handle public key advertisements specially
	if uint16(ev.Kind) == 39103 {
		if keyAd, err := directory.ParsePublicKeyAdvertisement(ev); err == nil {
			r.mu.Lock()
			r.publicKeyAds[keyAd.KeyID] = keyAd
			r.mu.Unlock()
		}
	}
}

// extractIdentityTag extracts an identity tag from an event if present.
func extractIdentityTag(ev *event.E) *directory.IdentityTag {
	if ev == nil || ev.Tags == nil {
		return nil
	}

	// Find the I tag
	for _, t := range *ev.Tags {
		if t != nil && len(t.T) > 0 && string(t.T[0]) == "I" {
			if identityTag, err := directory.ParseIdentityTag(t); err == nil {
				return identityTag
			}
		}
	}
	return nil
}

// cacheIdentityTag caches an identity tag mapping.
func (r *IdentityResolver) cacheIdentityTag(tag *directory.IdentityTag) {
	if tag == nil {
		return
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	identity := tag.NPubIdentity
	// For now, we use the identity as the delegate too since the structure is different
	// This should be updated when the IdentityTag structure is clarified
	delegate := identity

	// Store delegate -> identity mapping
	r.delegateToIdentity[delegate] = identity

	// Store identity -> delegates mapping
	if r.identityToDelegates[identity] == nil {
		r.identityToDelegates[identity] = make(map[string]bool)
	}
	r.identityToDelegates[identity][delegate] = true

	// Cache the full tag
	r.identityTagCache[delegate] = tag
}

// ResolveIdentity resolves the actual identity behind a public key.
//
// If the public key is a delegate, it returns the primary identity.
// If the public key is already an identity, it returns the input unchanged.
func (r *IdentityResolver) ResolveIdentity(pubkey string) string {
	r.mu.RLock()
	defer r.mu.RUnlock()

	if identity, ok := r.delegateToIdentity[pubkey]; ok {
		return identity
	}
	return pubkey
}

// ResolveEventIdentity resolves the actual identity behind an event's pubkey.
func (r *IdentityResolver) ResolveEventIdentity(ev *event.E) string {
	if ev == nil {
		return ""
	}
	return r.ResolveIdentity(string(ev.Pubkey))
}

// IsDelegateKey checks if a public key is a known delegate.
func (r *IdentityResolver) IsDelegateKey(pubkey string) bool {
	r.mu.RLock()
	defer r.mu.RUnlock()

	_, ok := r.delegateToIdentity[pubkey]
	return ok
}

// IsIdentityKey checks if a public key is a known identity (has delegates).
func (r *IdentityResolver) IsIdentityKey(pubkey string) bool {
	r.mu.RLock()
	defer r.mu.RUnlock()

	delegates, ok := r.identityToDelegates[pubkey]
	return ok && len(delegates) > 0
}

// GetDelegatesForIdentity returns all delegate keys for a given identity.
func (r *IdentityResolver) GetDelegatesForIdentity(identity string) (delegates []string) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	delegateMap, ok := r.identityToDelegates[identity]
	if !ok {
		return []string{}
	}

	delegates = make([]string, 0, len(delegateMap))
	for delegate := range delegateMap {
		delegates = append(delegates, delegate)
	}
	return
}

// GetIdentityTag returns the identity tag for a delegate key.
func (r *IdentityResolver) GetIdentityTag(delegate string) (*directory.IdentityTag, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	tag, ok := r.identityTagCache[delegate]
	if !ok {
		return nil, errorf.E("identity tag not found for delegate: %s", delegate)
	}
	return tag, nil
}

// GetPublicKeyAdvertisements returns all public key advertisements for an identity.
func (r *IdentityResolver) GetPublicKeyAdvertisements(identity string) (ads []*directory.PublicKeyAdvertisement) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	delegates := r.identityToDelegates[identity]
	ads = make([]*directory.PublicKeyAdvertisement, 0)

	for _, keyAd := range r.publicKeyAds {
		adIdentity := r.delegateToIdentity[string(keyAd.Event.Pubkey)]
		if adIdentity == "" {
			adIdentity = string(keyAd.Event.Pubkey)
		}

		if adIdentity == identity {
			ads = append(ads, keyAd)
			continue
		}

		// Check if the advertised key is a delegate
		if delegates != nil && delegates[keyAd.PublicKey] {
			ads = append(ads, keyAd)
		}
	}

	return
}

// GetPublicKeyAdvertisementByID returns a public key advertisement by key ID.
func (r *IdentityResolver) GetPublicKeyAdvertisementByID(keyID string) (*directory.PublicKeyAdvertisement, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	keyAd, ok := r.publicKeyAds[keyID]
	if !ok {
		return nil, errorf.E("public key advertisement not found: %s", keyID)
	}
	return keyAd, nil
}

// FilterEventsByIdentity filters events to only those signed by a specific identity or its delegates.
func (r *IdentityResolver) FilterEventsByIdentity(events []*event.E, identity string) (filtered []*event.E) {
	r.mu.RLock()
	delegates := r.identityToDelegates[identity]
	r.mu.RUnlock()

	filtered = make([]*event.E, 0)
	for _, ev := range events {
		pubkey := string(ev.Pubkey)
		if pubkey == identity {
			filtered = append(filtered, ev)
			continue
		}
		if delegates != nil && delegates[pubkey] {
			filtered = append(filtered, ev)
		}
	}

	return
}

// ClearCache clears all cached identity mappings.
func (r *IdentityResolver) ClearCache() {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.delegateToIdentity = make(map[string]string)
	r.identityToDelegates = make(map[string]map[string]bool)
	r.identityTagCache = make(map[string]*directory.IdentityTag)
	r.publicKeyAds = make(map[string]*directory.PublicKeyAdvertisement)
}

// Stats returns statistics about tracked identities and delegates.
type Stats struct {
	Identities   int // Number of primary identities
	Delegates    int // Number of delegate keys
	PublicKeyAds int // Number of public key advertisements
}

// GetStats returns statistics about the resolver's state.
func (r *IdentityResolver) GetStats() Stats {
	r.mu.RLock()
	defer r.mu.RUnlock()

	return Stats{
		Identities:   len(r.identityToDelegates),
		Delegates:    len(r.delegateToIdentity),
		PublicKeyAds: len(r.publicKeyAds),
	}
}
