package routing import ( "context" "encoding/json" "fmt" "trip-planner/internal/cache" "trip-planner/internal/metrics" "trip-planner/internal/yandex" ) // SearchCacheService handles caching and on-demand Yandex /search calls. type SearchCacheService struct { cache *cache.CacheAside yclient *yandex.Client metrics *metrics.Metrics } // NewSearchCacheService creates a new search cache service. func NewSearchCacheService(cacheStore cache.Cache, yclient *yandex.Client, m *metrics.Metrics) *SearchCacheService { return &SearchCacheService{ cache: cache.NewCacheAside(cacheStore, m), yclient: yclient, metrics: m, } } // SearchWithCache performs a route search with caching support. // It uses the cache-aside pattern: try cache first, then Yandex API, then write back to cache. func (s *SearchCacheService) SearchWithCache(ctx context.Context, from, to, date string, opts SearchOptions) (*yandex.Response, error) { // Generate cache key including far-term flag to distinguish near-term vs far-term searches farTermFlag := "near" if opts.FarTerm { farTermFlag = "far" } searchKey := cache.GetSearchKeyWithFarTerm(from, to, date, farTermFlag) // Try to get from cache first fetchFunc := func() ([]byte, error) { // If we reach here, it's a cache miss - perform on-demand Yandex /search call return s.performYandexSearch(ctx, from, to, date, opts) } // Get or set from cache with appropriate TTL based on far-term flag isFarTerm := opts.FarTerm data, err := s.cache.GetSearch(ctx, searchKey, fetchFunc, isFarTerm) if err != nil { return nil, fmt.Errorf("search cache get/set: %w", err) } // Parse the yandex.Response from cached data var result yandex.Response if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("failed to parse yandex response from cache: %w", err) } return &result, nil } // performYandexSearch makes the actual Yandex /search API call. func (s *SearchCacheService) performYandexSearch(ctx context.Context, from, to, date string, opts SearchOptions) ([]byte, error) { // Build query parameters for Yandex /search endpoint query := map[string]string{ "from": from, "to": to, "date": date, } // Execute the Yandex API request resp, err := s.yclient.Do(ctx, "GET", "/v3.0/search/", query) if err != nil { return nil, fmt.Errorf("yandex search failed: %w", err) } // Convert response to bytes for caching return convertResponseToBytes(resp) } // convertResponseToBytes converts Yandex API response to bytes for caching. func convertResponseToBytes(resp *yandex.Response) ([]byte, error) { if resp == nil { return nil, fmt.Errorf("nil response") } data, err := json.Marshal(resp) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return data, nil }