package com.tasteby.service;

import com.tasteby.util.IdGenerator;
import com.tasteby.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class VectorService {

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

    private final NamedParameterJdbcTemplate jdbc;
    private final OciGenAiService genAi;

    public VectorService(NamedParameterJdbcTemplate jdbc, OciGenAiService genAi) {
        this.jdbc = jdbc;
        this.genAi = genAi;
    }

    /**
     * Search similar restaurants by vector distance.
     */
    public List<Map<String, Object>> searchSimilar(String query, int topK, double maxDistance) {
        List<List<Double>> embeddings = genAi.embedTexts(List.of(query));
        // #293 — embeddings 빈/null 가드 (NPE/IndexOutOfBoundsException 방지)
        if (embeddings == null || embeddings.isEmpty()) return List.of();
        List<Double> first = embeddings.getFirst();
        if (first == null || first.isEmpty()) return List.of();

        // Convert to float array for Oracle VECTOR type
        float[] queryVec = new float[first.size()];
        for (int i = 0; i < queryVec.length; i++) {
            queryVec[i] = first.get(i).floatValue();
        }

        String sql = """
            SELECT rv.restaurant_id, rv.chunk_text,
                   VECTOR_DISTANCE(rv.embedding, :qvec, COSINE) AS dist
            FROM restaurant_vectors rv
            WHERE VECTOR_DISTANCE(rv.embedding, :qvec2, COSINE) <= :max_dist
            ORDER BY dist
            FETCH FIRST :k ROWS ONLY
        """;

        var params = new MapSqlParameterSource();
        params.addValue("qvec", queryVec);
        params.addValue("qvec2", queryVec);
        params.addValue("max_dist", maxDistance);
        params.addValue("k", topK);

        return jdbc.query(sql, params, (rs, rowNum) -> {
            Map<String, Object> m = new LinkedHashMap<>();
            m.put("restaurant_id", rs.getString("RESTAURANT_ID"));
            m.put("chunk_text", JsonUtil.readClob(rs.getObject("CHUNK_TEXT")));
            m.put("distance", rs.getDouble("DIST"));
            return m;
        });
    }

    /**
     * Save vector embeddings for a restaurant.
     *
     * #331 — N개 청크를 단일 batchUpdate 호출로 처리 (이전: N+1 INSERT round-trip).
     * UUID 생성은 IdGenerator.newId() 공통 유틸 사용 (인라인 변환 코드 제거).
     */
    public void saveRestaurantVectors(String restaurantId, List<String> chunks) {
        if (chunks.isEmpty()) return;

        List<List<Double>> embeddings = genAi.embedTexts(chunks);

        String sql = """
            INSERT INTO restaurant_vectors (id, restaurant_id, chunk_text, embedding)
            VALUES (:id, :rid, :chunk, :emb)
        """;

        SqlParameterSource[] batch = new SqlParameterSource[chunks.size()];
        for (int i = 0; i < chunks.size(); i++) {
            List<Double> emb = embeddings.get(i);
            float[] vec = new float[emb.size()];
            for (int j = 0; j < vec.length; j++) {
                vec[j] = emb.get(j).floatValue();
            }
            batch[i] = new MapSqlParameterSource()
                    .addValue("id", IdGenerator.newId())
                    .addValue("rid", restaurantId)
                    .addValue("chunk", chunks.get(i))
                    .addValue("emb", vec);
        }
        jdbc.batchUpdate(sql, batch);
    }

    /**
     * Build a text chunk for vector embedding from restaurant data.
     */
    public static List<String> buildChunks(String name, Map<String, Object> data, String videoTitle) {
        var parts = new ArrayList<String>();
        parts.add("식당: " + name);
        if (data.get("region") != null) parts.add("지역: " + data.get("region"));
        if (data.get("cuisine_type") != null) parts.add("음식 종류: " + data.get("cuisine_type"));
        if (data.get("foods_mentioned") instanceof List<?> foods && !foods.isEmpty()) {
            parts.add("메뉴: " + String.join(", ", foods.stream().map(Object::toString).toList()));
        }
        if (data.get("evaluation") != null) parts.add("평가: " + data.get("evaluation"));
        if (data.get("price_range") != null) parts.add("가격대: " + data.get("price_range"));
        parts.add("영상: " + videoTitle);
        return List.of(String.join("\n", parts));
    }
}
