import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DataType, MilvusClient } from '@zilliz/milvus2-sdk-node';
import axios from 'axios';
import OpenAI from 'openai';
import * as cheerio from 'cheerio';
import * as path from "path";
import * as fs from "fs";
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { GoogleGenerativeAI } from '@google/generative-ai';

import { ModelService } from '../../model/model.service';
import { CommonService } from '../../common/common.service';
import { Types } from 'mongoose';
import * as dto from './dto';

import { config } from 'dotenv'
config()

@Injectable()
/**
 * KnowledgeBaseService is responsible for managing interactions with the knowledge base,
 * including vector-based operations and integrations with external services like Milvus
 * and OpenAI. It handles embedding generation, vector storage, and retrieval operations.
 *
 * Key Responsibilities:
 * - Initialize and manage connections to Milvus for vector storage.
 * - Generate embeddings using OpenAI's API.
 * - Provide methods for interacting with the knowledge base.
 *
 * Dependencies:
 * - ModelService: Provides access to data models.
 * - CommonService: Offers shared utility functions.
 * - MilvusClient: Manages interactions with the Milvus vector database.
 * - OpenAI: Handles embedding generation using OpenAI's API.
 *
 * Properties:
 * - vectorDimension: The dimension of the vector embeddings (default: 728).
 * - milvusClient: The Milvus client instance for vector database operations.
 * - openAi: The OpenAI instance for generating embeddings.
 * - openAIModel: The embedding generation instance (OpenAI).
 */
export class KnowledgeBaseService {

    private vectorDimension: number;
    private milvusClient: any;
    private openAi: any;
    private openAIModel: any;
    private genAI: GoogleGenerativeAI;
    private genAIEmbed: any;
    private collectionName: string;

    constructor(
        private readonly Model: ModelService,
        private readonly CommonService: CommonService,
    ) {
        this.vectorDimension = 728;
        this.milvusClient = new MilvusClient({
            address: process.env.MILVUS_DB_URL
        });
        this.collectionName = process.env.MILVUS_COLLECTION_NAME

        this.openAIModel = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
        this.genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
        this.genAIEmbed = this.genAI.getGenerativeModel({
            model: 'embedding-001',
        });
    }

    /**
     * Generates a vector embedding for the provided content using OpenAI's embedding model.
     * The embedding is normalized to match the predefined vector dimension by either truncating
     * or padding the vector as necessary.
     *
     * @param {string} content - The input text content to generate an embedding for.
     * @returns {Promise<number[]>} - A promise that resolves to the generated embedding vector.
     * @throws {Error} - Throws an error if the embedding generation fails.
     */
    async embedContent(content: string) {
        try {
            content = content.trim().replace(/\s+/g, ' ');
            let embedding: any;

            embedding = await this.openAIModel.embeddings.create({
                model: "text-embedding-3-small",
                input: content,
            });
            embedding = embedding.data[0].embedding;

            if (embedding.length > this.vectorDimension) {
                embedding = embedding.slice(0, this.vectorDimension);
            } else if (embedding.length < this.vectorDimension) {
                const padding = new Array(this.vectorDimension - embedding.length).fill(0);
                embedding = embedding.concat(padding);
            }

            return embedding;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Splits a given text into smaller chunks of a specified maximum size, ensuring that
     * chunks do not break in the middle of a word. This is useful for processing large texts
     * in smaller, manageable pieces.
     */
    getChunks = (text: string): string[] => {
        try {
            const chunks: string[] = [];
            let start = 0;
            const maxChunkSize = 9000;
            while (start < text.length) {
                let end = start + maxChunkSize;
                // Ensure we don’t cut in the middle of a word
                if (end < text.length) {
                    const lastSpace = text.lastIndexOf(' ', end);
                    if (lastSpace > start) end = lastSpace;
                }
                chunks.push(text.slice(start, end));
                start = end + 1;
            }
            return chunks;
        } catch (error) {
            throw error;
        }
    };

    /**
     * Checks if a collection exists in the Milvus database and creates it if it doesn't.
     * The collection is configured with specific fields, including a primary key (`id`),
     * metadata, vector embeddings (`values`), and a document identifier (`documentId`).
     * If the collection is created, an index is also created for efficient querying.
     *
     * @returns {Promise<boolean>} - Returns `true` if the collection is created or already exists.
     * @throws {Error} - Throws an error if the collection creation or index creation fails.
     */
    async createCollectionIfNotExists() {
        try {
            const collections = await this.milvusClient.showCollections();
            console.log('milvus collections', collections.collections)
            // Define the dimension of the vector embeddings
            const dim = 728;

            const field_name = [
                {
                    name: 'id', // Primary key field
                    data_type: DataType.VarChar,
                    is_primary_key: true,
                    max_length: 50,
                },
                {
                    name: 'metadata',
                    data_type: DataType.VarChar,
                    max_length: 10000,
                },
                {
                    name: 'values',
                    data_type: DataType.FloatVector,
                    type_params: { dim: `${dim}` },
                },
                {
                    name: 'documentId',
                    data_type: DataType.VarChar,
                    max_length: 1000,
                },
            ]

            if (!collections.collection_names.includes(this.collectionName)) {
                console.log(`Creating collection: ${this.collectionName}`);
                const test = await this.milvusClient.createCollection({
                    collection_name: this.collectionName,
                    fields: field_name,
                });
                console.log('JSON.stringify(test)', JSON.stringify(test))
                console.log(`Collection created: ${this.collectionName}`);

                await this.checkAndCreateIndexMilvus();
                return true;
            }
        } catch (error) {
            throw error;
        }
    }

    /**
     * Creates an index on the specified field (`values`) in the Milvus collection if it doesn't already exist.
     * The index is created using the IVF_FLAT algorithm with L2 distance metric, which is suitable for
     * efficient similarity search on vector embeddings.
     *
     * @returns {Promise<any>} - Returns the response from the Milvus client after creating the index.
     * @throws {Error} - Throws an error if the index creation fails.
     */
    async checkAndCreateIndexMilvus() {
        try {
            const indexParams = {
                collection_name: this.collectionName,
                field_name: 'values',
                index_type: 'IVF_FLAT',
                metric_type: 'L2',
                params: {
                    nlist: 128
                },
            };

            const response = await this.milvusClient.createIndex(indexParams);

            return response
        } catch (error) {
            console.error(`Error checking or creating index: ${error.message}`);
            throw error;
        }
    }

    /**
     * Class responsible for managing Milvus collections.
     * This class provides methods to check the existence of a collection,
     * verify its load status, and load it if necessary.
     */
    async loadCollectionIfNeeded() {
        try {
            const collections = await this.milvusClient.showCollections();
            const collection = collections.data.find((col) => col.name === this.collectionName);

            if (!collection) {
                throw new Error(`Collection '${this.collectionName}' does not exist.`);
            }

            if (!collection.loaded) {
                console.log(`Collection '${this.collectionName}' not loaded. Loading it now...`);
                await this.milvusClient.loadCollection({ collection_name: this.collectionName });
                console.log(`Collection '${this.collectionName}' loaded successfully.`);
            }
            return true;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Adds data to Milvus by generating an embedding, creating a collection if needed,
     * and inserting the data into the vector database.
     *
     * @param {any} content - The textual content to be embedded and stored.
     * @param {string} document_id - The ID of the associated document.
     * @param {string} knowledge_base_id - The ID of the knowledge base.
     * @returns {Promise<string>} - Returns the error code from Milvus if any, otherwise undefined.
     * @throws {Error} - Throws an error if any step fails.
     */
    async addDataToMilvus(content: any, document_id: string, knowledge_base_id: string): Promise<string> {
        try {
            console.log('Milvus collectionName =>>', this.collectionName)
            content = content.trim().replace(/\s+/g, ' ');
            const embedding = await this.embedContent(content);
            const id = `docs_${+new Date()}`
            const milvus_payload = [
                {
                    id: id,
                    values: embedding,
                    metadata: content,
                    documentId: document_id
                },
            ];
            await this.createCollectionIfNotExists();
            // Insert data into Milvus
            const response = await this.milvusClient.insert({
                collection_name: this.collectionName,
                data: milvus_payload,
            });
            // Update document reference in the database
            await this.Model.documentsModel.findOneAndUpdate(
                { _id: new Types.ObjectId(document_id) },
                { $addToSet: { document_ids: id } },
                { new: true }
            );
            return response?.status?.error_code;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Deletes records from Milvus based on document IDs.
     *
     * @param {string | string[]} documentIds - A single document ID or an array of document IDs to delete.
     * @returns {Promise<boolean>} - Returns true if the deletion request was sent successfully.
     * @throws {Error} - Logs and handles errors if the deletion fails.
     */
    async deleteByDocumentIdsMilvus(documentIds: string | string[]) {
        try {
            if (!Array.isArray(documentIds)) {
                documentIds = [documentIds];
            }
            const expr = `documentId in [${documentIds.map(id => `'${id}'`).join(', ')}]`;

            const response = await this.milvusClient.deleteEntities({
                collection_name: this.collectionName,
                expr: expr,
            });
            console.log(`Deletion response for documentIds "${documentIds}":`, response?.delete_cnt);
            return true;
        } catch (error) {
            console.error(`Error deleting data for documentIds "${documentIds}":`, error.message);
        }
    }

    /**
     * Uploads multiple text chunks as embeddings to Milvus in batches.
     *
     * @param {string[]} chunks - An array of text chunks to be embedded and stored.
     * @param {string} documentId - The ID of the associated document.
     * @param {string} knowledge_base_id - The ID of the knowledge base.
     * @returns {Promise<void>} - Resolves when all embeddings are successfully uploaded.
     * @throws {Error} - Throws an error if any embedding operation fails.
     */
    async uploadEmbeddings(chunks: string[], documentId: string, knowledge_base_id: string) {
        const batchSize = 10;
        const embeddingPromises: any[] = [];

        for (const chunk of chunks) {
            embeddingPromises.push(
                this.addDataToMilvus(chunk, documentId, knowledge_base_id)
            );

            if (embeddingPromises.length === batchSize) {
                await Promise.all(embeddingPromises);
                embeddingPromises.length = 0;
            }
        }

        if (embeddingPromises.length > 0) {
            await Promise.all(embeddingPromises);
        }
    }

    /**
     * Extracts all valid absolute links from the given Cheerio-parsed HTML document.
     *
     * @param {string} url - The base URL of the page being scraped.
     * @param {cheerio.CheerioAPI} $ - The Cheerio API instance for parsing HTML content.
     * @returns {string[]} - An array of extracted absolute URLs.
     * @throws {Error} - Throws an error if link extraction fails.
     */
    extractLinks(url: string, $: cheerio.CheerioAPI): string[] {
        try {
            const links: string[] = [];
            $('a').each((_, element) => {
                let href = $(element).attr('href');
                if (href && !href.startsWith('#')) {
                    const absoluteLink = new URL(href, url).href;
                    if (absoluteLink.startsWith(url)) {
                        links.push(absoluteLink);
                    }
                }
            });
            console.log('links', links.length);
            return links;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Creates a new knowledge base and assigns the requesting user as an admin.
     *
     * @param {dto.knowledgebase.knowledgeBaseCreate} body - The request body containing knowledge base details.
     * @param {any} req - The request object containing user data.
     * @returns {Promise<any>} - The created knowledge base document.
     * @throws {Error} - Throws an error if creation fails.
     */
    async knowledge_base_create(body: dto.knowledgebase.knowledgeBaseCreate, req: any): Promise<any> {
        try {
            const { _id: user_id } = req.user_data;
            const { name } = body;
            return await this.Model.knowledgeBaseModel.create({
                user_id: new Types.ObjectId(user_id),
                is_admin: true,
                name: name,
            });
        } catch (error) {
            throw error;
        }
    }

    /**
     * Retrieves knowledge bases with pagination support.
     *
     * @param {dto.knowledgebase.getKnowledgeBase} body - The request body containing pagination parameters.
     * @param {any} req - The request object containing user data.
     * @returns {Promise<{ count: number, data: any[] }>} - The retrieved knowledge bases and their count.
     * @throws {Error} - Throws an error if retrieval fails.
     */
    async knowledge_base_get(body: dto.knowledgebase.getKnowledgeBase, req: any): Promise<{ count: number; data: any[]; }> {
        try {
            const { _id: user_id } = req.user_data;
            const { pagination, limit } = body;
            const query = { is_admin: true };
            const options = await this.CommonService.set_options(pagination, limit);
            const project = { name: true, created_at: true }

            const data = await this.Model.knowledgeBaseModel.find(query, project, options);
            const count = await this.Model.knowledgeBaseModel.countDocuments(query);
            return { count: count, data: data };
        } catch (error) {
            throw error;
        }
    }

    /**
     * Retrieves documents from a specific knowledge base with optional search and pagination.
     *
     * @param {string} knowledge_base_id - The ID of the knowledge base.
     * @param {dto.knowledgebase.getKnowledgeBase} body - The request body containing pagination and search parameters.
     * @param {any} req - The request object containing user data.
     * @returns {Promise<{ count: number, data: any[] }>} - The retrieved documents and their count.
     * @throws {Error} - Throws an error if retrieval fails.
     */
    async knowledge_base_documents(knowledge_base_id: string, body: dto.knowledgebase.getKnowledgeBase, req: any): Promise<{ count: number; data: any[]; }> {
        try {
            const { _id: user_id } = req.user_data;
            const { pagination, limit, search } = body;

            const options = await this.CommonService.set_options(pagination, limit);
            const query = {
                knowledge_base_id: new Types.ObjectId(knowledge_base_id),
                ...(search && { name: { $regex: search, $options: "i" } }),
            };

            const data = await this.Model.documentsModel.find(query, {}, options);
            const count = await this.Model.documentsModel.countDocuments(query);
            return { count: count, data: data };
        } catch (error) {
            throw error;
        }
    }

    /**
     * Deletes a document from both Milvus and MongoDB.
     *
     * @param {string} document_id - The ID of the document to delete.
     * @param {any} req - The request object.
     * @returns {Promise<{ message: string }>} - Confirmation message.
     * @throws {Error} - Throws an error if deletion fails.
     */
    async document_delete(document_id: string, req: any): Promise<{ message: string; }> {
        try {
            /** Delete from Milvus */
            await this.deleteByDocumentIdsMilvus(document_id);
            await this.Model.documentsModel.deleteOne({ _id: new Types.ObjectId(document_id) });
            return { message: "success" };
        } catch (error) {
            throw error;
        }
    }

    /**
     * Retrieves all document IDs associated with a given knowledge base.
     *
     * @param {string} knowledge_base_id - The ID of the knowledge base.
     * @returns {Promise<string[]>} - An array of document IDs.
     * @throws {Error} - Throws an error if retrieval fails.
     */
    async get_document_ids_by_knowledge_base(knowledge_base_id: string): Promise<string[]> {
        try {
            const document = await this.Model.documentsModel.find(
                { knowledge_base_id: new Types.ObjectId(knowledge_base_id) },
                {},
                { lean: true }
            );
            let document_id: string[] = [];
            document.map((doc: any) => document_id.push(doc?._id));
            return document_id;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Deletes a knowledge base along with all its associated documents from Milvus and MongoDB.
     *
     * @param {string} knowledge_base_id - The ID of the knowledge base to delete.
     * @param {any} req - The request object containing user data.
     * @returns {Promise<{ message: string }>} - Confirmation message.
     * @throws {Error} - Throws an error if deletion fails.
     */
    async knowledge_base_delete(knowledge_base_id: string, req: any): Promise<{ message: string; }> {
        try {
            let { _id: user_id } = req.user_data;
            const document_ids = await this.get_document_ids_by_knowledge_base(knowledge_base_id);

            const in_use = await this.Model.AgentModel.findOne({ knowledge_base_id: { $in: new Types.ObjectId(knowledge_base_id) }, is_deleted: false }, { name: 1 }, { lean: true });
            if (in_use) throw new HttpException(
                `This knowledge-base is in use/ assigned to ${in_use.name}! Please unassigned then to proceed.`,
                HttpStatus.BAD_REQUEST
            );

            /** Delete from Milvus */
            await this.deleteByDocumentIdsMilvus(document_ids);
            await this.Model.documentsModel.deleteMany({ knowledge_base_id: new Types.ObjectId(knowledge_base_id) });
            await this.Model.knowledgeBaseModel.deleteOne({ _id: new Types.ObjectId(knowledge_base_id) });

            return { message: "success" };
        } catch (error) {
            throw error;
        }
    }

    /**
     * Scrapes a webpage, extracts its content, uploads embeddings to Milvus,
     * and recursively follows links to a specified depth.
     *
     * @param {string} url - The URL of the page to scrape.
     * @param {string} title - The title of the document.
     * @param {string} knowledge_base_id - The ID of the associated knowledge base.
     * @param {number} [depth=1] - The maximum depth for recursive scraping.
     * @returns {Promise<number>} - The number of extracted links from the processed document.
     * @throws {Error} - Throws an error if the scraping or embedding process fails.
     */
    async scrapAndUpload(url: string, title: string, knowledge_base_id: string, depth: number = 1, visited = new Set()): Promise<number> {
        let document;
        if (depth === 0 || visited.has(url)) return;
        try {

            console.log('url =======> ', url)
            const { data: html } = await axios.get(url);
            const $ = cheerio.load(html);
            $('style').remove();
            $('[style]').removeAttr('style');
            $('script').remove();
            const textContent = $('body').text().trim();
            console.log(`Scraping ${url} at depth ${depth}`);

            if (!textContent) {
                throw new Error(`No meaningful content found at URL: ${url}`);
            }

            document = await this.Model.documentsModel.create({
                type: "TEXT",
                name: title,
                document_title: `${title}`,
                content: textContent.trim().replace(/\s+/g, ' '),
                ...(knowledge_base_id && { knowledge_base_id: new Types.ObjectId(knowledge_base_id) }),
            });

            const chunks = this.getChunks(textContent);
            await this.uploadEmbeddings(chunks, document._id.toString(), knowledge_base_id);

            let extract_urls = [];
            if (depth > 1) {
                const links = this.extractLinks(url, $);
                extract_urls = links;
                for (const link of links) {
                    title = link;
                    await this.scrapAndUpload(link, title, knowledge_base_id, depth - 1);
                }
            }

            return extract_urls.length // "Document processed successfully";
        } catch (error) {
            if (document?._id) {
                await this.Model.documentsModel.deleteOne({ _id: document._id });
            }
            console.error(`Error processing document "${title}" from URL "${url}":`, error.message);
            throw error;
        }
    }

    /**
     * Uploads a text document to the knowledge base and processes its embeddings for storage in Milvus.
     *
     * @param {string} text - The raw text content to be uploaded.
     * @param {string} title - The title of the document.
     * @param {string} knowledge_base_id - The ID of the associated knowledge base.
     * @returns {Promise<{ message: string }>} - Confirmation message upon successful upload.
     * @throws {Error} - Throws an error if the upload or processing fails.
     */
    async textUpload(text: string, title: string, knowledge_base_id: string): Promise<{ message: string; }> {
        let document = await this.Model.documentsModel.create({
            type: "TEXT",
            name: title,
            document_title: title,
            content: text,
            ...(knowledge_base_id && { knowledge_base_id: new Types.ObjectId(knowledge_base_id) })
        })

        try {
            text = text.trim().replace(/<\/?[^>]+(>|$)/g, " ");
            let embeddings = this.getChunks(text);
            let data_to_push = [];

            for (let i = 0; i < embeddings.length; i++) {
                const content = embeddings[i];
                let embedding = this.addDataToMilvus(content, document?._id?.toString(), knowledge_base_id);
                data_to_push.push(embedding);
                if (data_to_push.length === 10) {
                    let resolve = await Promise.all(data_to_push);
                    data_to_push = [];
                }
            }
            await Promise.all(data_to_push);
            return { message: "success" }
        } catch (error) {
            await this.Model.documentsModel.deleteOne({ _id: document?._id })
            throw error;
        }
    }

    /**
     * Splits a given document into smaller chunks for processing.
     *
     * @param {string} folder_path - The path to the document file.
     * @returns {Promise<any[]>} - Returns an array of split document chunks.
     * @throws {Error} - Throws an error if the file type is unsupported or processing fails.
     */
    async splitDocs(folder_path: any): Promise<any[]> {
        try {
            const fileExtension = path.extname(folder_path).toLowerCase();
            let loader;
            if (fileExtension === '.pdf') {
                loader = new PDFLoader(folder_path);
            } else if (fileExtension === '.docx') {
                loader = new DocxLoader(folder_path);
            } else {
                throw new Error(`Unsupported file type: ${fileExtension}`);
            }
            const docs = await loader.load();
            console.log("docs1", docs);
            const splitter = new RecursiveCharacterTextSplitter({
                chunkSize: 20000,
                chunkOverlap: 200,
            });
            const splitDocs = await splitter.splitDocuments(docs);
            console.log('splitDocs', splitDocs.length);
            console.log('splitDocs', splitDocs[0].pageContent.length);

            return splitDocs;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Uploads a PDF or DOCX file to the knowledge base and processes its content.
     *
     * @param {any} file - The uploaded file object containing file details and buffer data.
     * @param {string} knowledge_base_id - The ID of the knowledge base the document belongs to.
     * @returns {Promise<any[]>} - Returns an array of processed document chunks.
     * @throws {Error} - Throws an error if file processing fails.
     */
    async pdfUpload(file: any, knowledge_base_id: string): Promise<any[]> {
        let folder_path = path.resolve(__dirname + '/assets/all_pdfs/' + file.originalname);
        fs.writeFileSync(folder_path, file.buffer);

        let loader;
        const fileExtension = path.extname(folder_path).toLowerCase();

        // Select the appropriate document loader
        if (fileExtension === '.pdf') {
            loader = new PDFLoader(folder_path);
        } else if (fileExtension === '.docx') {
            loader = new DocxLoader(folder_path);
        } else {
            throw new Error(`Unsupported file type: ${fileExtension}`);
        }

        // Load the document and combine all page content into a single text string
        const docs = await loader.load();
        const combinedData = docs.map((page: { pageContent: string; }) => page.pageContent).join(' ');

        // Save document metadata to the database
        let document = await this.Model.documentsModel.create({
            type: "PDF",
            name: file.originalname,
            document_title: file.originalname,
            content: combinedData,
            knowledge_base_id: new Types.ObjectId(knowledge_base_id),
        });

        try {
            // Split document into smaller chunks
            const splitDocs = await this.splitDocs(folder_path);
            const batchSize = 1;
            let data_to_push = [];

            // Process each chunk and store embeddings
            for (let i = 0; i < splitDocs.length; i++) {
                const batch = splitDocs.slice(i, i + batchSize);
                for (let index = 0; index < batch.length; index++) {
                    const element = batch[index];
                    data_to_push.push(this.addDataToMilvus(element.pageContent, document?._id?.toString(), knowledge_base_id));

                    if (data_to_push.length === 10) {
                        await Promise.all(data_to_push);
                        data_to_push = [];
                    }
                }
            }
            await Promise.all(data_to_push);

            // Clean up the uploaded file
            fs.unlinkSync(folder_path);
            return splitDocs;
        } catch (error) {
            // Handle errors and ensure cleanup
            fs.unlink(folder_path, () => { });
            throw error;
        }
    }

    async getDataFromMilvus(queryVector: string, document_id: string) {
        try {
            console.time('Embedding generation');
            const vectorQuery = await this.embedContent(queryVector);
            console.timeEnd('Embedding generation');

            const document_ids = [document_id];

            const expr = `documentId in [${document_ids.map(id => `'${id}'`).join(', ')}]`;
            console.log('expr ===========> docIds', document_ids.length)


            await this.loadCollectionIfNeeded();

            const searchParams = {
                collection_name: this.collectionName,
                vectors: vectorQuery,
                search_params: {
                    anns_field: 'values',
                    topk: 3,
                    metric_type: 'L2',
                    params: JSON.stringify({ nprobe: 10 }),
                },
                expr: expr,
                output_fields: ['id', 'metadata', 'documentId'],
            };

            console.time('searchResponse vector');
            const searchResponse = await this.milvusClient.search(searchParams);
            console.timeEnd('searchResponse vector');

            return searchResponse.results.map((result) => ({
                score: result.score,
                documentId: result.documentId,
                metadata: result.metadata,
            }));
        } catch (error) {
            console.error(`Error querying Milvus: ${error.message}`, {
                stack: error.stack,
            });
            throw error;
        }
    }
}
