package at.procon.ted.service;

import at.procon.dip.domain.ted.service.TedNoticeProjectionService;
import at.procon.ted.config.TedProcessorProperties;
import at.procon.ted.event.DocumentSavedEvent;
import at.procon.ted.model.entity.*;
import at.procon.ted.repository.ProcurementDocumentRepository;
import at.procon.ted.util.HashUtils;
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.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.OffsetDateTime;
import java.util.Optional;

/**
 * Service for processing TED procurement documents.
 *
 * Handles the complete processing pipeline:
 * 1. Hash computation for idempotency check
 * 2. XML parsing and data extraction
 * 3. Database storage
 *
 * Note: Vectorization is triggered separately by VectorizationRoute via Camel wireTap.
 *
 * @author Martin.Schweitzer@procon.co.at and claude.ai
 */
@Service
@ConditionalOnRuntimeMode(RuntimeMode.LEGACY)
@RequiredArgsConstructor
@Slf4j
public class DocumentProcessingService {

    private final XmlParserService xmlParserService;
    private final ProcurementDocumentRepository documentRepository;
    private final ProcessingLogService processingLogService;
    private final TedProcessorProperties properties;
    private final ApplicationEventPublisher eventPublisher;

    /**
     * Process an XML document from the file system.
     * 
     * @param xmlContent The XML content
     * @param filename Source filename
     * @param filePath Source file path
     * @param fileSize File size in bytes
     * @return Processing result with document ID if successful
     */
    @Transactional
    public ProcessingResult processDocument(String xmlContent, String filename, String filePath, Long fileSize) {
        long startTime = System.currentTimeMillis();
        
        // Step 1: Compute document hash
        String documentHash = HashUtils.computeSha256(xmlContent);
        log.debug("Processing document {} with hash {}", filename, documentHash);

        // Step 2: Check for duplicate (idempotent processing)
        if (documentRepository.existsByDocumentHash(documentHash)) {
            log.debug("Duplicate document detected, skipping: {} (hash: {})", filename, documentHash);
            processingLogService.logEvent(
                    null, documentHash, ProcessingLog.EventType.DUPLICATE,
                    ProcessingLog.EventStatus.SKIPPED,
                    "Document already exists in database", null, filename,
                    (int) (System.currentTimeMillis() - startTime));
            return ProcessingResult.duplicate(documentHash);
        }

        try {
            // Step 3: Parse XML document
            ProcurementDocument document = xmlParserService.parseDocument(xmlContent);
            document.setDocumentHash(documentHash);
            document.setSourceFilename(filename);
            document.setSourcePath(filePath);
            document.setFileSizeBytes(fileSize);
            document.setProcessingDurationMs((int) (System.currentTimeMillis() - startTime));

            // Step 4: Save to database
            document = documentRepository.save(document);

            log.debug("Successfully processed document: {} -> {} (publication: {})",
                    filename, document.getId(), document.getPublicationId());

            // Log success
            processingLogService.logEvent(
                    document, documentHash, ProcessingLog.EventType.STORED,
                    ProcessingLog.EventStatus.SUCCESS,
                    "Document parsed and stored successfully", null, filename,
                    (int) (System.currentTimeMillis() - startTime));

            // Publish event to trigger vectorization AFTER transaction commit
            // This ensures document is visible in DB and avoids transaction isolation issues
            eventPublisher.publishEvent(new DocumentSavedEvent(document.getId(), document.getPublicationId()));
            log.debug("Document saved successfully, vectorization event published: {}", document.getId());

            return ProcessingResult.success(document.getId(), documentHash, document.getPublicationId());

        } catch (XmlParserService.XmlParsingException e) {
            log.error("Failed to parse XML document {}: {}", filename, e.getMessage());
            processingLogService.logEvent(
                    null, documentHash, ProcessingLog.EventType.ERROR,
                    ProcessingLog.EventStatus.FAILURE,
                    "XML parsing failed", e.getMessage(), filename,
                    (int) (System.currentTimeMillis() - startTime));
            return ProcessingResult.error(documentHash, "XML parsing failed: " + e.getMessage());

        } catch (Exception e) {
            log.error("Unexpected error processing document {}: {}", filename, e.getMessage(), e);
            processingLogService.logEvent(
                    null, documentHash, ProcessingLog.EventType.ERROR,
                    ProcessingLog.EventStatus.FAILURE,
                    "Processing failed", e.getMessage(), filename,
                    (int) (System.currentTimeMillis() - startTime));
            return ProcessingResult.error(documentHash, "Processing failed: " + e.getMessage());
        }
    }

    /**
     * Reprocess a document (e.g., after schema update).
     */
    @Transactional
    public Optional<ProcurementDocument> reprocessDocument(String publicationId) {
        return documentRepository.findByPublicationId(publicationId)
                .map(existing -> {
                    try {
                        // Re-parse the stored XML
                        ProcurementDocument updated = xmlParserService.parseDocument(existing.getXmlDocument());
                        
                        // Preserve identity and tracking fields
                        updated.setId(existing.getId());
                        updated.setDocumentHash(existing.getDocumentHash());
                        updated.setSourceFilename(existing.getSourceFilename());
                        updated.setSourcePath(existing.getSourcePath());
                        updated.setFileSizeBytes(existing.getFileSizeBytes());
                        updated.setCreatedAt(existing.getCreatedAt());
                        
                        // Reset vectorization
                        updated.setVectorizationStatus(VectorizationStatus.PENDING);
                        updated.setContentVector(null);
                        updated.setVectorizedAt(null);
                        updated.setVectorizationError(null);

                        documentRepository.save(updated);

                        // Note: Re-vectorization will be triggered automatically by the active scheduler
                        return updated;
                    } catch (Exception e) {
                        log.error("Failed to reprocess document {}: {}", publicationId, e.getMessage());
                        return null;
                    }
                });
    }

    /**
     * Result of document processing operation.
     */
    public record ProcessingResult(
            Status status,
            java.util.UUID documentId,
            String documentHash,
            String publicationId,
            String errorMessage
    ) {
        public enum Status {
            SUCCESS,
            DUPLICATE,
            ERROR
        }

        public static ProcessingResult success(java.util.UUID id, String hash, String pubId) {
            return new ProcessingResult(Status.SUCCESS, id, hash, pubId, null);
        }

        public static ProcessingResult duplicate(String hash) {
            return new ProcessingResult(Status.DUPLICATE, null, hash, null, null);
        }

        public static ProcessingResult error(String hash, String message) {
            return new ProcessingResult(Status.ERROR, null, hash, null, message);
        }

        public boolean isSuccess() {
            return status == Status.SUCCESS;
        }

        public boolean isDuplicate() {
            return status == Status.DUPLICATE;
        }

        public boolean isError() {
            return status == Status.ERROR;
        }
    }
}
