package common

import (
	"context"
	"oc-discovery/conf"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"
)

// SearchIdleTimeout returns the configured search idle timeout (default 5s).
func SearchIdleTimeout() time.Duration {
	if t := conf.GetConfig().SearchTimeout; t > 0 {
		return time.Duration(t) * time.Second
	}
	return 5 * time.Second
}

// searchEntry holds the lifecycle state for one active search.
type searchEntry struct {
	cancel      context.CancelFunc
	timer       *time.Timer
	idleTimeout time.Duration
}

// SearchTracker tracks one active search per user (peer or resource).
// Each search is keyed by a composite "user:searchID" so that a replaced
// search's late-arriving results can be told apart from the current one.
//
// Typical usage:
//
//	ctx, cancel := context.WithCancel(parent)
//	key := tracker.Register(userKey, cancel, idleTimeout)
//	defer tracker.Cancel(key)
//	// ... on each result: tracker.ResetIdle(key) + tracker.IsActive(key)
type SearchTracker struct {
	mu      sync.Mutex
	entries map[string]*searchEntry
}

func NewSearchTracker() *SearchTracker {
	return &SearchTracker{entries: map[string]*searchEntry{}}
}

// Register starts a new search for baseUser, cancelling any previous one.
// Returns the composite key "baseUser:searchID" to be used as the search identifier.
func (t *SearchTracker) Register(baseUser string, cancel context.CancelFunc, idleTimeout time.Duration) string {
	compositeKey := baseUser + ":" + uuid.New().String()
	t.mu.Lock()
	t.cancelByPrefix(baseUser)
	e := &searchEntry{cancel: cancel, idleTimeout: idleTimeout}
	e.timer = time.AfterFunc(idleTimeout, func() { t.Cancel(compositeKey) })
	t.entries[compositeKey] = e
	t.mu.Unlock()
	return compositeKey
}

// Cancel cancels the search(es) matching user (bare user key or composite key).
func (t *SearchTracker) Cancel(user string) {
	t.mu.Lock()
	t.cancelByPrefix(user)
	t.mu.Unlock()
}

// ResetIdle resets the idle timer for compositeKey after a response arrives.
func (t *SearchTracker) ResetIdle(compositeKey string) {
	t.mu.Lock()
	if e, ok := t.entries[compositeKey]; ok {
		e.timer.Reset(e.idleTimeout)
	}
	t.mu.Unlock()
}

// IsActive returns true if compositeKey is still the current active search.
func (t *SearchTracker) IsActive(compositeKey string) bool {
	t.mu.Lock()
	_, ok := t.entries[compositeKey]
	t.mu.Unlock()
	return ok
}

// cancelByPrefix cancels all entries whose key equals user or starts with "user:".
// Must be called with t.mu held.
func (t *SearchTracker) cancelByPrefix(user string) {
	for k, e := range t.entries {
		if k == user || strings.HasPrefix(k, user+":") {
			e.timer.Stop()
			e.cancel()
			delete(t.entries, k)
		}
	}
}
