/*
 * @author Dongmin.lee
 * @since 2026-04-10
 * @version 26.04.10
 * @see <pre>
 *  Copyright (C) 2007 by 313 DEV GRP, Inc - All Rights Reserved
 *  Unauthorized copying of this file, via any medium is strictly prohibited
 *  Proprietary and confidential
 *  Written by 313 developer group <313@313.co.kr>, December 2010
 * </pre>
 */
package com.arms.api.ai.search.service;

import com.arms.api.ai.search.model.vo.AiSearchDocumentVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.client.RequestOptions;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.common.unit.Fuzziness;
import org.opensearch.index.query.BoolQueryBuilder;
import org.opensearch.index.query.MultiMatchQueryBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.search.SearchHit;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Wiki 인덱스 키워드 검색 서비스 구현체
 *
 * <pre>
 * 처리 흐름:
 *   1. keywords (1개 이상) → 각 키워드별 multi_match 쿼리를 should(OR) 로 구성
 *   2. RestHighLevelClient 로 wiki 인덱스 검색
 *      - 검색 대상: 모든 필드 (*)
 *      - 유사 검색: fuzziness = AUTO (오타/유사 단어 허용)
 *      - 대소문자: lenient = true 로 무시
 *   3. SearchHit → AiSearchDocumentVO 매핑 후 반환
 * </pre>
 */
@Slf4j
@Service
@AllArgsConstructor
public class WikiSearchServiceImpl implements WikiSearchService {

    private static final String WIKI_INDEX  = "wiki";
    private static final int    MAX_RESULTS = 2;

    private final RestHighLevelClient restHighLevelClient;

    // ──────────────────────────────────────────────────────────────
    // searchByKeywords
    // ──────────────────────────────────────────────────────────────

    @Override
    public List<AiSearchDocumentVO> searchByKeywords(List<String> keywords) {

        log.info("WikiSearchServiceImpl :: searchByKeywords | keywords={}", keywords);

        if (ObjectUtils.isEmpty(keywords)) {
            log.warn("WikiSearchServiceImpl :: searchByKeywords | keywords 가 비어 있어 빈 목록 반환");
            return Collections.emptyList();
        }

        try {
            SearchRequest  request  = buildSearchRequest(keywords);
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

            List<AiSearchDocumentVO> results = mapToDocuments(response, keywords);

            log.info("WikiSearchServiceImpl :: searchByKeywords 완료 | keywords={}, 결과 수={}",
                    keywords, results.size());

            return results;

        } catch (Exception e) {
            log.error("WikiSearchServiceImpl :: searchByKeywords 오류 | keywords={} | error={}",
                    keywords, e.getMessage(), e);
            return Collections.emptyList();
        }
    }

    // ──────────────────────────────────────────────────────────────
    // Internal — 쿼리 빌드
    // ──────────────────────────────────────────────────────────────

    /**
     * 검색 요청을 생성합니다.
     *
     * <pre>
     * 쿼리 구조:
     * {
     *   "bool": {
     *     "should": [
     *       {
     *         "multi_match": {
     *           "query": "키워드1",
     *           "fields": ["*"],
     *           "fuzziness": "AUTO",   ← 유사 검색 (편집 거리 자동 계산)
     *           "lenient": true        ← 타입 불일치 무시 (대소문자 포함)
     *         }
     *       },
     *       {
     *         "multi_match": { "query": "키워드2", ... }
     *       }
     *     ],
     *     "minimum_should_match": 1    ← 키워드 중 1개 이상 매칭
     *   }
     * }
     * </pre>
     */
    private SearchRequest buildSearchRequest(List<String> keywords) {

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        for (String keyword : keywords) {
            if (keyword == null || keyword.isBlank()) continue;

            // multi_match: 모든 필드 대상, 유사 검색, 대소문자 무시
            MultiMatchQueryBuilder multiMatch = QueryBuilders
                    .multiMatchQuery(keyword, "*")
                    .fuzziness(Fuzziness.AUTO)  // 유사 검색 (AUTO: 길이에 따라 편집 거리 자동 결정)
                    .lenient(true)              // 타입 불일치(숫자/날짜 필드) 무시, 대소문자 무시 포함
                    .type(MultiMatchQueryBuilder.Type.BEST_FIELDS); // 가장 점수 높은 필드 기준

            boolQuery.should(multiMatch);
        }

        boolQuery.minimumShouldMatch(1); // 하나 이상의 키워드 매칭 필수
        boolQuery.must(QueryBuilders.termQuery("recent", true)); // recent=true 문서만

        SearchSourceBuilder source = new SearchSourceBuilder()
                .query(boolQuery)
                .size(MAX_RESULTS);

        return new SearchRequest(new String[]{WIKI_INDEX}, source);
    }

    // ──────────────────────────────────────────────────────────────
    // Internal — 결과 매핑
    // ──────────────────────────────────────────────────────────────

    private List<AiSearchDocumentVO> mapToDocuments(SearchResponse response, List<String> keywords) {

        List<AiSearchDocumentVO> results = new ArrayList<>();

        if (response.getHits() == null || response.getHits().getHits() == null) {
            return results;
        }

        String keywordJoined = String.join(", ", keywords);

        for (SearchHit hit : response.getHits().getHits()) {

            Map<String, Object> source = hit.getSourceAsMap();
            if (source == null) source = new HashMap<>();

            Map<String, Object> metadata = new HashMap<>();
            metadata.put("index", hit.getIndex());

            results.add(AiSearchDocumentVO.builder()
                    .id(hit.getId())
                    .keyword(keywordJoined)
                    .title(getStringValue(source, "wiki_id"))
                    .content(getStringValue(source, "contents"))
                    .score((double) hit.getScore())
                    .metadata(metadata)
                    .build());
        }

        return results;
    }

    private String getStringValue(Map<String, Object> source, String key) {
        Object val = source.get(key);
        return val != null ? val.toString() : "";
    }
}
