package at.procon.ted.service;

import at.procon.dip.domain.access.DocumentVisibility;
import at.procon.dip.domain.document.ContentRole;
import at.procon.dip.domain.document.DocumentFamily;
import at.procon.dip.domain.document.DocumentStatus;
import at.procon.dip.domain.document.DocumentType;
import at.procon.dip.domain.document.RepresentationType;
import at.procon.dip.domain.document.SourceType;
import at.procon.dip.domain.document.StorageType;
import at.procon.dip.domain.document.entity.Document;
import at.procon.dip.domain.document.entity.DocumentContent;
import at.procon.dip.domain.document.entity.DocumentEmbedding;
import at.procon.dip.domain.document.entity.DocumentEmbeddingModel;
import at.procon.dip.domain.document.entity.DocumentSource;
import at.procon.dip.domain.document.entity.DocumentTextRepresentation;
import at.procon.dip.domain.document.repository.DocumentContentRepository;
import at.procon.dip.domain.document.repository.DocumentEmbeddingRepository;
import at.procon.dip.domain.document.repository.DocumentRepository;
import at.procon.dip.domain.document.repository.DocumentSourceRepository;
import at.procon.dip.domain.document.repository.DocumentTextRepresentationRepository;
import at.procon.dip.domain.document.service.DocumentEmbeddingService;
import at.procon.dip.domain.document.service.DocumentService;
import at.procon.dip.domain.document.service.command.RegisterEmbeddingModelCommand;
import at.procon.ted.config.TedProcessorProperties;
import at.procon.ted.model.entity.ProcurementDocument;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Phase 2 bridge that dual-writes TED documents into the generic DOC persistence backbone.
 * In Phase 3 the same bridge also becomes the generic root for TED projections even when the
 * generic vectorization route is disabled.
 */
@Service
@ConditionalOnRuntimeMode(RuntimeMode.LEGACY)
@RequiredArgsConstructor
@Slf4j
public class TedPhase2GenericDocumentService {

    private final TedProcessorProperties properties;
    private final DocumentRepository documentRepository;
    private final DocumentContentRepository contentRepository;
    private final DocumentSourceRepository sourceRepository;
    private final DocumentTextRepresentationRepository representationRepository;
    private final DocumentEmbeddingRepository embeddingRepository;
    private final DocumentService documentService;
    private final DocumentEmbeddingService embeddingService;

    /**
     * Phase 2 compatibility API used by manual trigger flows. Returns the embedding id when the
     * generic vectorization pipeline is enabled; otherwise returns {@code null} after ensuring the
     * generic document root exists.
     */
    @Transactional
    public UUID registerOrRefreshTedDocument(ProcurementDocument tedDocument) {
        return syncTedDocument(tedDocument).embeddingId();
    }

    /**
     * Ensures the generic DOC document exists and is refreshed from the current legacy TED document state.
     */
    @Transactional
    public UUID ensureGenericTedDocument(ProcurementDocument tedDocument) {
        return syncTedDocument(tedDocument).documentId();
    }

    @Transactional
    public TedGenericDocumentSyncResult syncTedDocument(ProcurementDocument tedDocument) {
        if (!isGenericTedSyncEnabled()) {
            return new TedGenericDocumentSyncResult(null, null, null);
        }

        Document document = documentRepository.findByDedupHash(tedDocument.getDocumentHash())
                .orElseGet(() -> createGenericDocument(tedDocument));

        document.setDocumentType(DocumentType.TED_NOTICE);
        document.setDocumentFamily(DocumentFamily.PROCUREMENT);
        document.setVisibility(DocumentVisibility.PUBLIC);
        document.setStatus(DocumentStatus.REPRESENTED);
        document.setTitle(tedDocument.getProjectTitle());
        document.setSummary(tedDocument.getProjectDescription());
        document.setLanguageCode(tedDocument.getLanguageCode());
        document.setMimeType("application/xml");
        document.setBusinessKey(buildBusinessKey(tedDocument));
        document.setDedupHash(tedDocument.getDocumentHash());
        document = documentRepository.save(document);

        ensureTedSource(document, tedDocument);
        DocumentContent originalContent = ensureOriginalContent(document, tedDocument);
        DocumentTextRepresentation representation = ensurePrimaryRepresentation(document, originalContent, tedDocument);

        UUID embeddingId = null;

        log.debug("Phase 2 DOC bridge ensured generic TED document {} without embedding queue", document.getId());
        return new TedGenericDocumentSyncResult(document.getId(), embeddingId, representation.getId());
    }

    private boolean isGenericTedSyncEnabled() {
        return properties.getProjection().isEnabled();
    }

    private Document createGenericDocument(ProcurementDocument tedDocument) {
        return documentService.create(new at.procon.dip.domain.document.service.command.CreateDocumentCommand(
                null,
                DocumentVisibility.PUBLIC,
                DocumentType.TED_NOTICE,
                DocumentFamily.PROCUREMENT,
                DocumentStatus.REPRESENTED,
                tedDocument.getProjectTitle(),
                tedDocument.getProjectDescription(),
                tedDocument.getLanguageCode(),
                "application/xml",
                buildBusinessKey(tedDocument),
                tedDocument.getDocumentHash()
        ));
    }

    private void ensureTedSource(Document document, ProcurementDocument tedDocument) {
        String externalId = tedDocument.getPublicationId() != null ? tedDocument.getPublicationId() : tedDocument.getId().toString();
        boolean sourceExists = sourceRepository.findByDocument_Id(document.getId()).stream()
                .anyMatch(existing -> externalId.equals(existing.getExternalSourceId()));
        if (sourceExists) {
            return;
        }

        DocumentSource source = DocumentSource.builder()
                .document(document)
                .sourceType(SourceType.FILE_SYSTEM)
                .externalSourceId(externalId)
                .sourceUri(tedDocument.getSourcePath())
                .sourceFilename(tedDocument.getSourceFilename())
                .importBatchId("ted-phase2")
                .receivedAt(OffsetDateTime.now())
                .build();
        sourceRepository.save(source);
    }

    private DocumentContent ensureOriginalContent(Document document, ProcurementDocument tedDocument) {
        List<DocumentContent> existing = contentRepository.findByDocument_IdAndContentRole(document.getId(), ContentRole.ORIGINAL);
        if (!existing.isEmpty()) {
            DocumentContent content = existing.get(0);
            content.setMimeType("application/xml");
            content.setStorageType(StorageType.DB_TEXT);
            content.setTextContent(tedDocument.getXmlDocument());
            content.setContentHash(tedDocument.getDocumentHash());
            content.setSizeBytes(tedDocument.getFileSizeBytes());
            return contentRepository.save(content);
        }

        DocumentContent content = DocumentContent.builder()
                .document(document)
                .contentRole(ContentRole.ORIGINAL)
                .storageType(StorageType.DB_TEXT)
                .mimeType("application/xml")
                .charsetName("UTF-8")
                .textContent(tedDocument.getXmlDocument())
                .contentHash(tedDocument.getDocumentHash())
                .sizeBytes(tedDocument.getFileSizeBytes())
                .build();
        return contentRepository.save(content);
    }

    private DocumentTextRepresentation ensurePrimaryRepresentation(Document document,
                                                                   DocumentContent originalContent,
                                                                   ProcurementDocument tedDocument) {
        DocumentTextRepresentation representation = representationRepository
                .findFirstByDocument_IdAndPrimaryRepresentationTrue(document.getId())
                .orElseGet(DocumentTextRepresentation::new);

        representation.setDocument(document);
        representation.setContent(originalContent);
        representation.setRepresentationType(RepresentationType.SEMANTIC_TEXT);
        representation.setBuilderKey(properties.getVectorization().getPrimaryRepresentationBuilderKey());
        representation.setLanguageCode(tedDocument.getLanguageCode());
        representation.setPrimaryRepresentation(true);
        representation.setTextBody(tedDocument.getTextContent() != null ? tedDocument.getTextContent() : tedDocument.getProjectDescription());
        representation.setTokenCount(null);
        representation.setChunkIndex(null);
        representation.setChunkStartOffset(null);
        representation.setChunkEndOffset(null);
        return representationRepository.save(representation);
    }

    private DocumentEmbedding ensurePendingEmbedding(Document document, DocumentTextRepresentation representation) {
        DocumentEmbeddingModel model = embeddingService.registerModel(new RegisterEmbeddingModelCommand(
                properties.getVectorization().getModelName(),
                null,
                properties.getVectorization().getModelName(),
                properties.getVectorization().getDimensions(),
                null,
                false,
                true
        ));

        return embeddingRepository.findByRepresentation_IdAndModel_Id(representation.getId(), model.getId())
                .map(existing -> {
                    existing.setDocument(document);
                    existing.setRepresentation(representation);
                    existing.setModel(model);
                    existing.setEmbeddingStatus(at.procon.dip.domain.document.EmbeddingStatus.PENDING);
                    existing.setErrorMessage(null);
                    existing.setEmbeddedAt(null);
                    return embeddingRepository.save(existing);
                })
                .orElseGet(() -> embeddingService.createPendingEmbedding(document.getId(), representation.getId(), model.getId()));
    }

    private String buildBusinessKey(ProcurementDocument tedDocument) {
        if (tedDocument.getPublicationId() != null && !tedDocument.getPublicationId().isBlank()) {
            return "TED:publication:" + tedDocument.getPublicationId();
        }
        if (tedDocument.getNoticeUrl() != null && !tedDocument.getNoticeUrl().isBlank()) {
            return "TED:url:" + tedDocument.getNoticeUrl();
        }
        return "TED:hash:" + tedDocument.getDocumentHash();
    }

    public record TedGenericDocumentSyncResult(
            UUID documentId,
            UUID embeddingId,
            UUID primaryRepresentationId
    ) {
    }
}
