package cache import ( "context" "encoding/json" "log/slog" "time" "github.com/metamorphosis-dev/samsa/internal/contracts" ) // EngineCache wraps Cache with per-engine tier-aware Get/Set operations. type EngineCache struct { cache *Cache overrides map[string]time.Duration } // NewEngineCache creates a new EngineCache with optional TTL overrides. // If overrides is nil, default tier durations are used. func NewEngineCache(cache *Cache, overrides map[string]time.Duration) *EngineCache { return &EngineCache{ cache: cache, overrides: overrides, } } // Get retrieves a cached engine response. Returns (zero value, false) if not // found or if cache is disabled. func (ec *EngineCache) Get(ctx context.Context, engine, queryHash string) (CachedEngineResponse, bool) { key := engineCacheKey(engine, queryHash) data, ok := ec.cache.GetBytes(ctx, key) if !ok { return CachedEngineResponse{}, false } var cached CachedEngineResponse if err := json.Unmarshal(data, &cached); err != nil { ec.cache.logger.Warn("engine cache hit but unmarshal failed", "key", key, "error", err) return CachedEngineResponse{}, false } ec.cache.logger.Debug("engine cache hit", "key", key, "engine", engine) return cached, true } // Set stores an engine response in the cache with the engine's tier TTL. func (ec *EngineCache) Set(ctx context.Context, engine, queryHash string, resp contracts.SearchResponse) { if !ec.cache.Enabled() { return } data, err := json.Marshal(resp) if err != nil { ec.cache.logger.Warn("engine cache set: marshal failed", "engine", engine, "error", err) return } tier := EngineTier(engine, ec.overrides) key := engineCacheKey(engine, queryHash) cached := CachedEngineResponse{ Engine: engine, Response: data, StoredAt: time.Now(), } cachedData, err := json.Marshal(cached) if err != nil { ec.cache.logger.Warn("engine cache set: wrap marshal failed", "key", key, "error", err) return } ec.cache.SetBytes(ctx, key, cachedData, tier.Duration) } // IsStale returns true if the cached response is older than the tier's TTL. func (ec *EngineCache) IsStale(cached CachedEngineResponse, engine string) bool { tier := EngineTier(engine, ec.overrides) return time.Since(cached.StoredAt) > tier.Duration } // Logger returns the logger for background refresh logging. func (ec *EngineCache) Logger() *slog.Logger { return ec.cache.logger } // engineCacheKey builds the cache key for an engine+query combination. func engineCacheKey(engine, queryHash string) string { return "samsa:resp:" + engine + ":" + queryHash }