package com.tasteby.service;

import com.tasteby.domain.Restaurant;
import com.tasteby.mapper.SearchMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class SearchService {

    private static final Logger log = LoggerFactory.getLogger(SearchService.class);

    private final SearchMapper searchMapper;
    private final RestaurantService restaurantService;
    private final VectorService vectorService;
    private final CacheService cache;

    public SearchService(SearchMapper searchMapper,
                          RestaurantService restaurantService,
                          VectorService vectorService,
                          CacheService cache) {
        this.searchMapper = searchMapper;
        this.restaurantService = restaurantService;
        this.vectorService = vectorService;
        this.cache = cache;
    }

    public List<Restaurant> search(String q, String mode, int limit) {
        String key = cache.makeKey("search", "q=" + q, "m=" + mode, "l=" + limit);
        String cached = cache.getRaw(key);
        if (cached != null) {
            try {
                var mapper = new com.fasterxml.jackson.databind.ObjectMapper();
                return mapper.readValue(cached, new com.fasterxml.jackson.core.type.TypeReference<List<Restaurant>>() {});
            } catch (Exception ignored) {}
        }

        List<Restaurant> result;
        switch (mode) {
            case "semantic" -> result = semanticSearch(q, limit);
            case "hybrid" -> {
                var kw = keywordSearch(q, limit);
                var sem = semanticSearch(q, limit);
                Set<String> seen = new HashSet<>();
                var merged = new ArrayList<Restaurant>();
                for (var r : kw) { if (seen.add(r.getId())) merged.add(r); }
                for (var r : sem) { if (seen.add(r.getId())) merged.add(r); }
                result = merged.size() > limit ? merged.subList(0, limit) : merged;
            }
            default -> result = keywordSearch(q, limit);
        }

        cache.set(key, result);
        return result;
    }

    private List<Restaurant> keywordSearch(String q, int limit) {
        String pattern = "%" + q + "%";
        List<Restaurant> results = searchMapper.keywordSearch(pattern, limit);
        if (!results.isEmpty()) {
            attachChannels(results);
        }
        return results;
    }

    private List<Restaurant> semanticSearch(String q, int limit) {
        try {
            var similar = vectorService.searchSimilar(q, Math.max(30, limit * 3), 0.57);
            if (similar.isEmpty()) return List.of();

            Set<String> seen = new LinkedHashSet<>();
            for (var s : similar) {
                seen.add((String) s.get("restaurant_id"));
            }

            List<Restaurant> results = new ArrayList<>();
            for (String rid : seen) {
                if (results.size() >= limit) break;
                var r = restaurantService.findById(rid);
                if (r != null && r.getLatitude() != null) {
                    results.add(r);
                }
            }
            return results;
        } catch (Exception e) {
            log.warn("Semantic search failed, falling back to keyword: {}", e.getMessage());
            return keywordSearch(q, limit);
        }
    }

    private void attachChannels(List<Restaurant> restaurants) {
        List<String> ids = restaurants.stream().map(Restaurant::getId).filter(Objects::nonNull).toList();
        if (ids.isEmpty()) return;

        var channelRows = searchMapper.findChannelsByRestaurantIds(ids);
        Map<String, List<String>> chMap = new HashMap<>();
        for (var row : channelRows) {
            String rid = (String) row.getOrDefault("restaurant_id", row.get("RESTAURANT_ID"));
            String ch = (String) row.getOrDefault("channel_name", row.get("CHANNEL_NAME"));
            if (rid != null && ch != null) {
                chMap.computeIfAbsent(rid, k -> new ArrayList<>()).add(ch);
            }
        }
        for (var r : restaurants) {
            r.setChannels(chMap.getOrDefault(r.getId(), List.of()));
        }
    }
}
