package com.vaessl.app.vector;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.stereotype.Service;
import com.vaessl.app.shared.ServiceItem;
import com.vaessl.app.shared.ServiceType;

/**
 * Embeds {@link ServiceItem}s into the shared vector store and prunes entries that no longer
 * exist upstream. Kept stateless (no instance fields besides {@code vectorStore}) since this is
 * a singleton bean shared across concurrent sync runs; callers own the per-sync ID accumulator.
 */
@Service
public class EmbeddingService {

    private final VectorStore vectorStore;

    public EmbeddingService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    /**
     * Embeds and upserts {@code items} into the vector store, appending each item's ID to
     * {@code currentItemIds} so the caller can later prune stale entries via
     * {@link #deleteStaleVectorEntries}. Safe to call repeatedly (e.g. once per page) against the
     * same accumulator across a single sync run.
     *
     * @param items the items to embed for this batch
     * @param serviceType the originating service, stored in each document's metadata
     * @param connectionId the connection these items belong to
     * @param currentItemIds accumulator collecting every item ID seen so far in this sync run
     */
    public void vectorizeData(List<ServiceItem> items, ServiceType serviceType, Long connectionId,
            List<String> currentItemIds) {
        List<Document> documents = new ArrayList<>();

        for (ServiceItem item : items) {
            documents.add(toDocument(item, serviceType, connectionId));
            currentItemIds.add(item.id());
        }
        vectorStore.add(documents);
    }

    private Document toDocument(ServiceItem item, ServiceType serviceType, Long connectionId) {
        String extraData = buildExtraData(item.extraData());
        Map<String, Object> metadata = buildMetadata(item, serviceType, connectionId);
        String content = buildContent(item, extraData);
        return new Document(connectionId + ":" + item.id(), content, metadata);
    }

    private String buildExtraData(Map<String, Object> extraData) {
        if (extraData == null) {
            return "";
        }
        StringBuilder data = new StringBuilder();
        for (Map.Entry<String, Object> entry : extraData.entrySet()) {
            if (entry.getValue() == null || entry.getValue().toString().isEmpty()) {
                continue;
            }
            if (!data.isEmpty()) {
                data.append(" \n");
            }
            data.append(entry.getKey()).append(": ").append(entry.getValue());
        }
        return data.toString();
    }

    private Map<String, Object> buildMetadata(ServiceItem item, ServiceType serviceType,
            Long connectionId) {
        Map<String, Object> metadata = new HashMap<>();
        metadata.put("connectionId", connectionId);
        metadata.put("serviceType", serviceType.name());
        metadata.put("itemId", item.id());
        return metadata;
    }

    private String buildContent(ServiceItem item, String extraData) {
        StringBuilder content = new StringBuilder("Title: ").append(item.title());
        if (item.description() != null && !item.description().isEmpty()) {
            content.append("\nDescription: ").append(item.description());
        }
        if (!extraData.isEmpty()) {
            content.append("\nExtradata: ").append(extraData);
        }
        return content.toString();
    }

    /**
     * Deletes every vector for {@code connectionId} whose item ID is not in
     * {@code currentItemIds}, then clears the accumulator. Must be called once, after every page
     * for this sync run has gone through {@link #vectorizeData}, not per page — otherwise items
     * from pages other than the most recent one would look stale and get deleted too.
     *
     * @param connectionId the connection to prune stale vectors for
     * @param currentItemIds every item ID seen across the full sync run; cleared after this call
     */
    public void deleteStaleVectorEntries(Long connectionId, List<String> currentItemIds) {
        FilterExpressionBuilder b = new FilterExpressionBuilder();
        vectorStore.delete(b.and(b.eq("connectionId", connectionId),
                b.nin("itemId", new ArrayList<>(currentItemIds))).build());

        currentItemIds.clear();
    }
}
