package com.cloudhandson.ddsbackoffice.service;

import com.cloudhandson.ddsbackoffice.config.DdsProperties;
import com.cloudhandson.ddsbackoffice.domain.DdsMcpEndUserPrincipal;
import com.cloudhandson.ddsbackoffice.domain.DdsMcpVectorSearchResult;
import com.cloudhandson.vpdbackoffice.domain.vector.VectorQueryEmbedding;
import com.cloudhandson.vpdbackoffice.service.AppException;
import com.cloudhandson.vpdbackoffice.service.VectorKnowledgeService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/** Runs the DDS-protected vector query only inside a local END USER context. */
@Service
public class DdsMcpVectorSearchService {

  private static final Logger log = LoggerFactory.getLogger(DdsMcpVectorSearchService.class);
  private final VectorKnowledgeService vectorKnowledgeService;
  private final DdsProperties ddsProperties;
  private final DdsMcpContextExecutor contextExecutor;
  private final ObjectMapper objectMapper;

  public DdsMcpVectorSearchService(
      VectorKnowledgeService vectorKnowledgeService,
      DdsProperties ddsProperties,
      DdsMcpContextExecutor contextExecutor,
      ObjectMapper objectMapper
  ) {
    this.vectorKnowledgeService = vectorKnowledgeService;
    this.ddsProperties = ddsProperties;
    this.contextExecutor = contextExecutor;
    this.objectMapper = objectMapper;
  }

  public DdsMcpVectorSearchResult search(
      DdsMcpEndUserPrincipal principal,
      String query,
      int requestedLimit,
      String embeddingMode
  ) {
    VectorQueryEmbedding vector = vectorKnowledgeService.vectorizeQuery(query, embeddingMode);
    String embedding = embeddingFrom(vector);
    int limit = Math.max(1, Math.min(requestedLimit, 100));
    return contextExecutor.withContext(principal, connection -> {
      String sql = """
          SELECT chunk_id, document_id, chunk_no, title, chunk_text, source_uri, tech_tag, score
          FROM (
            SELECT d.chunk_id, d.document_id, d.chunk_no, d.title, d.chunk_text, d.source_uri,
                   d.tech_tag, VECTOR_DISTANCE(d.embedding, TO_VECTOR(?), COSINE) AS score
            FROM %s d
            WHERE d.embedding IS NOT NULL
            ORDER BY score
          )
          WHERE ROWNUM <= ?
          """.formatted(ddsProperties.vectorObject());
      try (PreparedStatement statement = connection.prepareStatement(sql)) {
        statement.setQueryTimeout((int) Math.max(1, ddsProperties.queryTimeout().toSeconds()));
        statement.setString(1, embedding);
        statement.setInt(2, limit);
        List<Map<String, Object>> rows = new ArrayList<>();
        log.info("DDS vector query started");
        try (ResultSet result = statement.executeQuery()) {
          while (result.next()) {
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("chunkId", result.getLong("chunk_id"));
            row.put("documentId", result.getString("document_id"));
            row.put("chunkNo", result.getInt("chunk_no"));
            row.put("title", result.getString("title"));
            row.put("chunkText", result.getString("chunk_text"));
            row.put("sourceUri", result.getString("source_uri"));
            row.put("techTag", result.getString("tech_tag"));
            row.put("score", result.getObject("score"));
            rows.add(Map.copyOf(row));
          }
        }
        log.info("DDS vector query completed with {} rows", rows.size());
        return new DdsMcpVectorSearchResult(vector.query(), vector.embeddingMode(), rows.size(), List.copyOf(rows));
      } catch (java.sql.SQLException exception) {
        throw new AppException("DDS_QUERY_FAILED: DDS 보호 객체를 조회할 수 없습니다.");
      }
    });
  }

  private String embeddingFrom(VectorQueryEmbedding vector) {
    try {
      JsonNode embedding = objectMapper.readTree(vector.requestBody()).path("embedding");
      if (!embedding.isArray() || embedding.isEmpty()) {
        throw new AppException("DDS_QUERY_FAILED: 검색 임베딩을 만들 수 없습니다.");
      }
      return embedding.toString();
    } catch (AppException exception) {
      throw exception;
    } catch (Exception exception) {
      throw new AppException("DDS_QUERY_FAILED: 검색 임베딩을 읽을 수 없습니다.");
    }
  }
}
