package com.tasteby.service;

import com.tasteby.domain.Restaurant;
import com.tasteby.mapper.RestaurantMapper;
import com.tasteby.util.IdGenerator;
import com.tasteby.util.JsonUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class RestaurantService {

    private final RestaurantMapper mapper;

    public RestaurantService(RestaurantMapper mapper) {
        this.mapper = mapper;
    }

    public List<Restaurant> findAll(int limit, int offset, String cuisine, String region, String channel) {
        return findAll(limit, offset, cuisine, region, channel, null, false);
    }

    // #533 — minMentions: 의미있는 언급 수가 N 미만인 식당 제외 (null/0이면 미적용)
    public List<Restaurant> findAll(int limit, int offset, String cuisine, String region, String channel,
                                    Integer minMentions, boolean includeHidden) {
        List<Restaurant> restaurants = mapper.findAll(limit, offset, cuisine, region, channel, minMentions, includeHidden);
        enrichRestaurants(restaurants);
        return restaurants;
    }

    // #322 — 검증 상태 갱신
    public void markHidden(String id, String reason) {
        mapper.updateVerification(id, 1, reason);
    }

    public void markVerifiedClean(String id) {
        mapper.updateVerification(id, 0, null);
    }

    public void clearHidden(String id) {
        mapper.clearHidden(id);
    }

    public List<Restaurant> findUnverified(int limit) {
        return mapper.findUnverified(limit);
    }

    public int countUnverified() {
        return mapper.countUnverified();
    }

    public List<Restaurant> findWithoutTabling() {
        return mapper.findWithoutTabling();
    }

    public List<Restaurant> findWithoutCatchtable() {
        return mapper.findWithoutCatchtable();
    }

    @Transactional
    public void resetTablingUrls() {
        mapper.resetTablingUrls();
    }

    @Transactional
    public void resetCatchtableUrls() {
        mapper.resetCatchtableUrls();
    }

    public Restaurant findById(String id) {
        Restaurant restaurant = mapper.findById(id);
        if (restaurant == null) return null;
        enrichRestaurants(List.of(restaurant));
        return restaurant;
    }

    public List<Map<String, Object>> findVideoLinks(String restaurantId) {
        return findVideoLinks(restaurantId, false);
    }

    public List<Map<String, Object>> findVideoLinks(String restaurantId, boolean includeWeak) {
        var rows = mapper.findVideoLinks(restaurantId, includeWeak);
        return rows.stream().map(row -> {
            var m = JsonUtil.lowerKeys(row);
            m.put("foods_mentioned", JsonUtil.parseStringList(m.get("foods_mentioned")));
            m.put("evaluation", JsonUtil.parseMap(m.get("evaluation")));
            m.put("guests", JsonUtil.parseStringList(m.get("guests")));
            return m;
        }).toList();
    }

    // #356 영상-식당 관련도
    public void updateLinkRelevance(String linkId, String relevance, String reason) {
        mapper.updateLinkRelevance(linkId, relevance, reason);
    }

    public Map<String, Object> findLinkContext(String linkId) {
        var row = mapper.findLinkContext(linkId);
        return row != null ? JsonUtil.lowerKeys(row) : null;
    }

    public List<Map<String, Object>> findUnevaluatedLinks(int limit) {
        return mapper.findUnevaluatedLinks(limit).stream()
                .map(JsonUtil::lowerKeys)
                .toList();
    }

    public int countUnevaluatedLinks() {
        return mapper.countUnevaluatedLinks();
    }

    // #359 1단계 — google_place_id 중복 그룹 (참조 카운트 동봉)
    public List<Map<String, Object>> findDuplicatePlaceIdGroups() {
        var rows = mapper.findDuplicatePlaceIdRows().stream()
                .map(JsonUtil::lowerKeys)
                .toList();
        Map<String, List<Map<String, Object>>> grouped = new LinkedHashMap<>();
        for (var r : rows) {
            String key = (String) r.get("google_place_id");
            grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(r);
        }
        List<Map<String, Object>> out = new ArrayList<>(grouped.size());
        for (var e : grouped.entrySet()) {
            out.add(Map.of("google_place_id", e.getKey(), "items", e.getValue()));
        }
        return out;
    }

    public void update(String id, Map<String, Object> fields) {
        mapper.updateFields(id, fields);
    }

    @Transactional
    public void delete(String id) {
        mapper.deleteVectors(id);
        mapper.deleteReviews(id);
        mapper.deleteFavorites(id);
        mapper.deleteVideoRestaurants(id);
        mapper.deleteRestaurant(id);
    }

    public String upsert(Map<String, Object> data) {
        String placeId = (String) data.get("google_place_id");
        String existingId = null;
        if (placeId != null && !placeId.isBlank()) {
            existingId = mapper.findIdByPlaceId(placeId);
        }
        if (existingId == null) {
            existingId = mapper.findIdByName((String) data.get("name"));
        }

        Restaurant r = Restaurant.builder()
                .name(truncateBytes((String) data.get("name"), 200))
                .address(truncateBytes((String) data.get("address"), 500))
                .region((String) data.get("region"))
                .latitude(data.get("latitude") instanceof Number n ? n.doubleValue() : null)
                .longitude(data.get("longitude") instanceof Number n ? n.doubleValue() : null)
                .cuisineType((String) data.get("cuisine_type"))
                .priceRange((String) data.get("price_range"))
                .googlePlaceId(placeId)
                .phone((String) data.get("phone"))
                .website((String) data.get("website"))
                .businessStatus((String) data.get("business_status"))
                .rating(data.get("rating") instanceof Number n ? n.doubleValue() : null)
                .ratingCount(data.get("rating_count") instanceof Number n ? n.intValue() : null)
                .build();

        if (existingId != null) {
            r.setId(existingId);
            mapper.updateRestaurant(r);
            return existingId;
        } else {
            String newId = IdGenerator.newId();
            r.setId(newId);
            mapper.insertRestaurant(r);
            return newId;
        }
    }

    public String linkVideoRestaurant(String videoId, String restaurantId, List<String> foods, String evaluation, List<String> guests) {
        String id = IdGenerator.newId();
        String foodsJson = foods != null ? JsonUtil.toJson(foods) : null;
        String guestsJson = guests != null ? JsonUtil.toJson(guests) : null;
        String evalJson = JsonUtil.normalizeEvaluation(evaluation);
        mapper.linkVideoRestaurant(id, videoId, restaurantId, foodsJson, evalJson, guestsJson);
        return id;
    }

    public void updateCuisineType(String id, String cuisineType) {
        mapper.updateCuisineType(id, cuisineType);
    }

    public void updateFoodsMentioned(String id, String foods) {
        mapper.updateFoodsMentioned(id, foods);
    }

    public List<Map<String, Object>> findForRemapCuisine() {
        return mapper.findForRemapCuisine();
    }

    public List<Map<String, Object>> findForRemapFoods() {
        return mapper.findForRemapFoods();
    }

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

        // Channels
        List<Map<String, Object>> channelRows = mapper.findChannelsByRestaurantIds(ids);
        Map<String, List<String>> channelMap = 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) {
                channelMap.computeIfAbsent(rid, k -> new ArrayList<>()).add(ch);
            }
        }

        // Foods
        List<Map<String, Object>> foodRows = mapper.findFoodsByRestaurantIds(ids);
        Map<String, Set<String>> foodMap = new HashMap<>();
        for (var row : foodRows) {
            String rid = (String) row.getOrDefault("restaurant_id", row.get("RESTAURANT_ID"));
            Object foods = row.getOrDefault("foods_mentioned", row.get("FOODS_MENTIONED"));
            if (rid != null && foods != null) {
                List<String> parsed = JsonUtil.parseStringList(foods);
                foodMap.computeIfAbsent(rid, k -> new LinkedHashSet<>()).addAll(parsed);
            }
        }

        for (var r : restaurants) {
            r.setChannels(channelMap.getOrDefault(r.getId(), List.of()));
            Set<String> foods = foodMap.get(r.getId());
            r.setFoodsMentioned(foods != null ? new ArrayList<>(foods) : List.of());
        }
    }

    private String truncateBytes(String s, int maxBytes) {
        if (s == null) return null;
        byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
        if (bytes.length <= maxBytes) return s;
        return new String(bytes, 0, maxBytes, StandardCharsets.UTF_8);
    }
}
