package com.emoon.knowledge.service.impl;

import com.emoon.common.vector.domain.bo.StoreEmbeddingBo;
import com.emoon.common.xinference.XinferenceClient;
import com.emoon.knowledge.service.VectorStoreService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.weaviate.client.Config;
import io.weaviate.client.WeaviateClient;
import io.weaviate.client.base.Result;
import io.weaviate.client.v1.data.model.WeaviateObject;
import io.weaviate.client.v1.schema.model.Property;
import io.weaviate.client.v1.schema.model.Schema;
import io.weaviate.client.v1.schema.model.WeaviateClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

/**
 * 向量库管理服务实现
 *
 * @author wangkang
 * @date 2026-01-26
 */
@Slf4j
@Service
public class VectorStoreServiceImpl implements VectorStoreService {

    @Value("${weaviate.protocol}")
    private String weaviateProtocol;

    @Value("${weaviate.host}")
    private String weaviateHost;

    @Value("${weaviate.port}")
    private String weaviatePort;

    @Value("${weaviate.className}")
    private String weaviateClassName;

    @Value("${embedding.base-url}")
    private String embeddingBaseUrl;

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final com.emoon.knowledge.service.IKnowledgeAttachService knowledgeAttachService;

    public VectorStoreServiceImpl(@Lazy com.emoon.knowledge.service.IKnowledgeAttachService knowledgeAttachService) {
        this.knowledgeAttachService = knowledgeAttachService;
    }

    @Override
    public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
        String kid = storeEmbeddingBo.getKid();
        String fullClassName = weaviateClassName + kid;

        // 初始化 Weaviate 客户端
        String host = weaviateHost + ":" + weaviatePort;
        WeaviateClient client = new WeaviateClient(new Config(weaviateProtocol, host));
        createSchema(kid, storeEmbeddingBo.getVectorModelName());

        List<String> chunkList = storeEmbeddingBo.getChunkList();
        String docId = storeEmbeddingBo.getDocId();
        if (docId == null || docId.trim().isEmpty()) {
            throw new IllegalArgumentException("docId 不能为空");
        }

        XinferenceClient xinferenceClient = new XinferenceClient(storeEmbeddingBo.getBaseUrl());
        String modelName = storeEmbeddingBo.getEmbeddingModelName();

        // 模型名称映射：将 bge-large-zh-v1.5 映射为 bge-large-zh（API 期望的模型名称）
        if (modelName != null && (modelName.contains("bge-large-zh-v1.5") || modelName.equals("bge-large-zh-v1.5"))) {
            modelName = "bge-large-zh-v1.5";
        }

        // 兼容旧逻辑：处理 quentinz/bge-large-zh-v1.5 格式
        if (modelName != null && modelName.contains("quentinz/bge-large-zh-v1.5")) {
            modelName = "bge-large-zh-v1.5";
        }

        for (int i = 0; i < chunkList.size(); i++) {
            String text = chunkList.get(i);
            if (text == null || text.trim().isEmpty()) {
                log.warn("跳过空文本: {}", i);
                continue;
            }
            List<Double> vector;
            try {
                vector = xinferenceClient.getEmbedding(modelName, text);
            } catch (IOException e) {
                throw new RuntimeException("生成向量失败: " + e.getMessage(), e);
            }

            // 创建 Weaviate 对象
            Map<String, Object> properties = new HashMap<>();
            properties.put("text", text);
            properties.put("indexFilterable", true);
            properties.put("indexSearchable", true);
            properties.put("docId", docId);
            properties.put("filename", storeEmbeddingBo.getFileName());
            properties.put("chunkIndex", i);

            HashMap<String, Object> hashMap = new HashMap<>();
            hashMap.put("docId", docId);
            hashMap.put("filename", storeEmbeddingBo.getFileName());
            hashMap.put("chunkIndex", i);
            try {
                String jsonString = objectMapper.writeValueAsString(hashMap);
                properties.put("metadata", jsonString);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }

            try {
                Result<WeaviateObject> result = client.data().creator()
                        .withClassName(fullClassName)
                        .withProperties(properties)
                        .withVector(toFloatArray(vector))
                        .run();
                if (result.hasErrors()) {
                    throw new RuntimeException("存储对象失败: " + result.getError().toString());
                }
            } catch (Exception e) {
                throw new RuntimeException("存储向量失败: " + e.getMessage(), e);
            }
        }
    }

    /**
     * 创建 Schema
     */
    private void createSchema(String kid, String modelName) {
        String fullClassName = weaviateClassName + kid;

        // Initialize Weaviate client
        String host = weaviateHost + ":" + weaviatePort;
        WeaviateClient client = new WeaviateClient(new Config(weaviateProtocol, host));

        // Check if schema exists
        Result<Schema> existingSchema = client.schema().getter().run();
        if (existingSchema.hasErrors()) {
            throw new RuntimeException("Failed to get schema: " + existingSchema.getError().toString());
        }

        // Check if class exists
        boolean classExists = existingSchema.getResult().getClasses().stream()
                .anyMatch(c -> c.getClassName().equals(fullClassName));
        if (!classExists) {
            WeaviateClass weaviateClass = WeaviateClass.builder()
                    .className(fullClassName)
                    .properties(Arrays.asList(
                            Property.builder().name("text").dataType(Arrays.asList("text")).build(),
                            Property.builder().name("indexFilterable").dataType(Arrays.asList("boolean")).build(),
                            Property.builder().name("indexSearchable").dataType(Arrays.asList("boolean")).build(),
                            Property.builder().name("docId").dataType(Arrays.asList("string")).build(),
                            Property.builder().name("filename").dataType(Arrays.asList("string")).build(),
                            Property.builder().name("metadata").dataType(Arrays.asList("string")).build(),
                            Property.builder().name("chunkIndex").dataType(Arrays.asList("int")).build()
                    ))
                    .vectorizer("none")
                    .build();
            Schema.builder()
                    .classes(Collections.singletonList(weaviateClass))
                    .build();
            client.schema().classCreator().withClass(weaviateClass).run();
        } else {
            WeaviateClass existingClass = existingSchema.getResult().getClasses().stream()
                    .filter(c -> c.getClassName().equals(fullClassName))
                    .findFirst()
                    .orElse(null);
            boolean hasDocId = existingClass.getProperties().stream()
                    .anyMatch(p -> p.getName().equals("docId"));
            if (!hasDocId) {
                Property docIdProperty = Property.builder()
                        .name("docId")
                        .dataType(Arrays.asList("string"))
                        .build();
                client.schema().propertyCreator()
                        .withClassName(fullClassName)
                        .withProperty(docIdProperty)
                        .run();
            }
            boolean filename = existingClass.getProperties().stream()
                    .anyMatch(p -> p.getName().equals("filename"));
            if (!filename) {
                Property docIdProperty = Property.builder()
                        .name("filename")
                        .dataType(Arrays.asList("string"))
                        .build();
                client.schema().propertyCreator()
                        .withClassName(fullClassName)
                        .withProperty(docIdProperty)
                        .run();
            }
            boolean chunkIndex = existingClass.getProperties().stream()
                    .anyMatch(p -> p.getName().equals("chunkIndex"));
            if (!chunkIndex) {
                Property docIdProperty = Property.builder()
                        .name("chunkIndex")
                        .dataType(Arrays.asList("int"))
                        .build();
                client.schema().propertyCreator()
                        .withClassName(fullClassName)
                        .withProperty(docIdProperty)
                        .run();
            }
        }
    }

    /**
     * 根据文档ID删除向量数据
     */
    @Override
    public void removeByDocId(String docId) {
        // 通过 docId 查询附件信息获取 kid
        com.emoon.knowledge.domain.bo.KnowledgeAttachBo knowledgeAttachBo = new com.emoon.knowledge.domain.bo.KnowledgeAttachBo();
        knowledgeAttachBo.setDocId(docId);
        String kid = null;
        try {
            java.util.List<com.emoon.knowledge.domain.vo.KnowledgeAttachVo> knowledgeAttachVoList =
                knowledgeAttachService.queryList(knowledgeAttachBo);
            if (knowledgeAttachVoList != null && !knowledgeAttachVoList.isEmpty()) {
                kid = knowledgeAttachVoList.get(0).getKid();
            }
        } catch (Exception e) {
            log.warn("查询附件信息失败，将直接使用 docId 作为 kid: {}", e.getMessage());
        }

        if (kid == null || kid.trim().isEmpty()) {
            log.warn("无法获取 kid，跳过向量数据删除");
            return;
        }

        String fullClassName = weaviateClassName + kid;
        String host = weaviateHost + ":" + weaviatePort;
        WeaviateClient client = new WeaviateClient(new Config(weaviateProtocol, host));

        try {
            // 构造删除请求
            client.batch()
                    .objectsBatchDeleter()
                    .withClassName(fullClassName)
                    .withWhere(io.weaviate.client.v1.filters.WhereFilter.builder()
                            .path("docId")
                            .operator("Equal")
                            .valueText(docId)
                            .build())
                    .run();
            log.info("成功删除向量数据，docId: {}, className: {}", docId, fullClassName);
        } catch (Exception e) {
            log.error("删除向量数据失败，docId: {}, className: {}", docId, fullClassName, e);
            throw new RuntimeException("删除向量数据失败: " + e.getMessage(), e);
        }
    }

    /**
     * 将 Double 列表转换为 Float 数组
     */
    private static Float[] toFloatArray(List<Double> doubleList) {
        Float[] floatArray = new Float[doubleList.size()];
        for (int i = 0; i < doubleList.size(); i++) {
            floatArray[i] = doubleList.get(i).floatValue();
        }
        return floatArray;
    }
}

