package at.procon.dip.vectorization.service;


import at.procon.dip.domain.document.DocumentStatus;
import at.procon.dip.domain.document.EmbeddingStatus;
import at.procon.dip.domain.document.entity.DocumentEmbedding;
import at.procon.dip.domain.document.repository.DocumentEmbeddingRepository;
import at.procon.dip.domain.document.service.DocumentService;

import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import at.procon.ted.config.LegacyVectorizationProperties;

import at.procon.ted.model.entity.VectorizationStatus;
import at.procon.ted.repository.ProcurementDocumentRepository;
import at.procon.ted.service.VectorizationService;

import java.time.OffsetDateTime;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Legacy generic vectorization processor that works on DOC text representations and DOC embeddings.
 * <p>
 * The service keeps the existing TED semantic search operational by optionally dual-writing completed
 * embeddings back into the legacy TED procurement_document vector columns, resolved by document hash.
 */
@Service
@ConditionalOnRuntimeMode(RuntimeMode.LEGACY)
@RequiredArgsConstructor
@Slf4j
public class DocumentEmbeddingProcessingService {

    private final DocumentEmbeddingRepository embeddingRepository;
    private final DocumentService documentService;
    private final VectorizationService vectorizationService;
    private final LegacyVectorizationProperties properties;
    private final ProcurementDocumentRepository procurementDocumentRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EmbeddingPayload prepareEmbeddingForVectorization(UUID embeddingId) {
        DocumentEmbedding embedding = embeddingRepository.findDetailedById(embeddingId)
                .orElseThrow(() -> new IllegalArgumentException("Unknown embedding id: " + embeddingId));

        if (embedding.getEmbeddingStatus() == EmbeddingStatus.PROCESSING) {
            log.debug("Embedding {} is already PROCESSING, skipping duplicate queue entry", embeddingId);
            return null;
        }

        embedding.setEmbeddingStatus(EmbeddingStatus.PROCESSING);
        embedding.setErrorMessage(null);
        embeddingRepository.save(embedding);

        String textBody = embedding.getRepresentation().getTextBody();
        if (textBody == null || textBody.isBlank()) {
            embedding.setEmbeddingStatus(EmbeddingStatus.SKIPPED);
            embedding.setErrorMessage("No text representation available");
            embedding.setEmbeddedAt(OffsetDateTime.now());
            embeddingRepository.save(embedding);
            documentService.updateStatus(embedding.getDocument().getId(), DocumentStatus.REPRESENTED);
            return null;
        }

        int maxLength = properties.getMaxTextLength();
        if (textBody.length() > maxLength) {
            log.debug("Truncating representation {} for embedding {} from {} to {} chars",
                    embedding.getRepresentation().getId(), embeddingId, textBody.length(), maxLength);
            textBody = textBody.substring(0, maxLength);
        }

        return new EmbeddingPayload(
                embedding.getId(),
                embedding.getDocument().getId(),
                embedding.getDocument().getDedupHash(),
                textBody,
                embedding.getModel().getDimensions(),
                embedding.getModel().isQueryPrefixRequired(),
                embedding.getRepresentation().getId()
        );
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveEmbedding(UUID embeddingId, float[] embedding, Integer tokenCount) {
        DocumentEmbedding loaded = embeddingRepository.findDetailedById(embeddingId)
                .orElseThrow(() -> new IllegalArgumentException("Unknown embedding id: " + embeddingId));

        int expectedDimensions = loaded.getModel().getDimensions();
        if (embedding == null || embedding.length != expectedDimensions) {
            throw new IllegalArgumentException("Invalid embedding dimension for " + embeddingId +
                    ": expected " + expectedDimensions + ", got " + (embedding == null ? 0 : embedding.length));
        }

        String vectorString = vectorizationService.floatArrayToVectorString(embedding);
        embeddingRepository.updateEmbeddingVector(embeddingId, embedding, tokenCount, embedding.length, null);
        documentService.updateStatus(loaded.getDocument().getId(), DocumentStatus.INDEXED);

        if (properties.isDualWriteLegacyTedVectors()) {
            dualWriteLegacyTedVector(loaded, vectorString, tokenCount);
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void markAsFailed(UUID embeddingId, String errorMessage) {
        DocumentEmbedding loaded = embeddingRepository.findDetailedById(embeddingId)
                .orElseThrow(() -> new IllegalArgumentException("Unknown embedding id: " + embeddingId));

        embeddingRepository.updateEmbeddingStatus(embeddingId, EmbeddingStatus.FAILED, errorMessage, null);
        documentService.updateStatus(loaded.getDocument().getId(), DocumentStatus.FAILED);

        if (properties.isDualWriteLegacyTedVectors()) {
            procurementDocumentRepository.findByDocumentHash(loaded.getDocument().getDedupHash())
                    .ifPresent(doc -> procurementDocumentRepository.updateVectorizationStatus(
                            doc.getId(), VectorizationStatus.FAILED, errorMessage, null));
        }
    }

    private void dualWriteLegacyTedVector(DocumentEmbedding embedding, String vectorString, Integer tokenCount) {
        String dedupHash = embedding.getDocument().getDedupHash();
        if (dedupHash == null || dedupHash.isBlank()) {
            return;
        }

        procurementDocumentRepository.findByDocumentHash(dedupHash)
                .ifPresentOrElse(
                        legacy -> {
                            procurementDocumentRepository.updateContentVector(legacy.getId(), vectorString, tokenCount);
                            log.debug("Dual-wrote embedding {} back to legacy TED document {}", embedding.getId(), legacy.getId());
                        },
                        () -> log.debug("No legacy TED document found for DOC embedding {} with dedup hash {}",
                                embedding.getId(), dedupHash)
                );
    }

    public record EmbeddingPayload(
            UUID embeddingId,
            UUID documentId,
            String dedupHash,
            String textContent,
            Integer expectedDimensions,
            boolean queryPrefixRequired,
            UUID representationId
    ) {
    }
}