package at.procon.ted.service;

import at.procon.dip.domain.ted.service.TedNoticeProjectionService;
import at.procon.ted.config.TedProcessorProperties;
import at.procon.ted.model.entity.ProcurementDocument;
import at.procon.ted.model.entity.ProcessingLog;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Service for batch processing of TED procurement documents.
 *
 * Processes all XML files from a Daily Package in a single transaction:
 * 1. Parse all XML files
 * 2. Check for duplicates
 * 3. Batch insert all new documents (saveAll)
 * 4. Trigger vectorization for all inserted documents
 *
 * This is much more efficient than processing documents one by one.
 *
 * @author Martin.Schweitzer@procon.co.at and claude.ai
 */
@Service
@ConditionalOnRuntimeMode(RuntimeMode.LEGACY)
@RequiredArgsConstructor
@Slf4j
public class BatchDocumentProcessingService {

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

    /**
     * Process a batch of XML files from a Daily Package.
     *
     * @param xmlFiles List of XML file paths to process
     * @return Batch processing result with statistics
     */
    @Transactional
    public BatchProcessingResult processBatch(List<Path> xmlFiles) {
        long startTime = System.currentTimeMillis();

        List<ProcurementDocument> documentsToInsert = new ArrayList<>();
        List<String> duplicateHashes = new ArrayList<>();
        List<ProcessingError> errors = new ArrayList<>();

        log.debug("Processing batch of {} XML files", xmlFiles.size());

        // Step 1: Parse all XML files and check for duplicates
        for (Path xmlFile : xmlFiles) {
            try {
                // Read XML content
                String xmlContent = Files.readString(xmlFile, StandardCharsets.UTF_8);
                String filename = xmlFile.getFileName().toString();
                String filePath = xmlFile.toString();
                long fileSize = Files.size(xmlFile);

                // Compute document hash
                String documentHash = HashUtils.computeSha256(xmlContent);

                // Check for duplicate
                if (documentRepository.existsByDocumentHash(documentHash)) {
                    log.debug("Duplicate document detected, skipping: {} (hash: {})", filename, documentHash);
                    duplicateHashes.add(documentHash);

                    processingLogService.logEvent(
                            null, documentHash, ProcessingLog.EventType.DUPLICATE,
                            ProcessingLog.EventStatus.SKIPPED,
                            "Document already exists in database", null, filename, 0);
                    continue;
                }

                // Parse XML document
                ProcurementDocument document = xmlParserService.parseDocument(xmlContent);
                document.setDocumentHash(documentHash);
                document.setSourceFilename(filename);
                document.setSourcePath(filePath);
                document.setFileSizeBytes(fileSize);

                documentsToInsert.add(document);

            } catch (IOException e) {
                log.warn("Failed to read XML file {}: {}", xmlFile, e.getMessage());
                errors.add(new ProcessingError(xmlFile.toString(), "File read error: " + e.getMessage()));

            } catch (XmlParserService.XmlParsingException e) {
                log.warn("Failed to parse XML file {}: {}", xmlFile, e.getMessage());
                errors.add(new ProcessingError(xmlFile.toString(), "XML parsing error: " + e.getMessage()));

                String hash = "unknown";
                try {
                    String xmlContent = Files.readString(xmlFile, StandardCharsets.UTF_8);
                    hash = HashUtils.computeSha256(xmlContent);
                } catch (IOException ignored) {}

                processingLogService.logEvent(
                        null, hash, ProcessingLog.EventType.ERROR,
                        ProcessingLog.EventStatus.FAILURE,
                        "XML parsing failed", e.getMessage(), xmlFile.getFileName().toString(), 0);

            } catch (Exception e) {
                log.error("Unexpected error processing file {}: {}", xmlFile, e.getMessage(), e);
                errors.add(new ProcessingError(xmlFile.toString(), "Unexpected error: " + e.getMessage()));
            }
        }

        // Step 2: Batch insert all new documents
        List<UUID> insertedDocumentIds = new ArrayList<>();
        if (!documentsToInsert.isEmpty()) {
            log.info("Batch inserting {} new documents into database", documentsToInsert.size());

            List<ProcurementDocument> savedDocuments = documentRepository.saveAll(documentsToInsert);

            // Log success for each document
            for (ProcurementDocument doc : savedDocuments) {
                insertedDocumentIds.add(doc.getId());

                processingLogService.logEvent(
                        doc, doc.getDocumentHash(), ProcessingLog.EventType.STORED,
                        ProcessingLog.EventStatus.SUCCESS,
                        "Document parsed and stored successfully (batch)", null,
                        doc.getSourceFilename(), 0);
            }

            log.info("Successfully inserted {} documents in batch", savedDocuments.size());
        }

        // Step 3: Vectorization will be picked up by VectorizationRoute scheduler
        // No need to publish individual events - the scheduler checks for PENDING documents
        // This avoids creating 149k+ inflight exchanges in the queue
        if (!insertedDocumentIds.isEmpty()) {
            log.debug("Inserted {} documents with vectorization_status=PENDING, " +
                    "will be picked up by vectorization scheduler", insertedDocumentIds.size());
        }

        long duration = System.currentTimeMillis() - startTime;
        log.info("Batch processing completed: {} inserted, {} duplicates, {} errors in {}ms",
                insertedDocumentIds.size(), duplicateHashes.size(), errors.size(), duration);

        return new BatchProcessingResult(
                insertedDocumentIds.size(),
                duplicateHashes.size(),
                errors.size(),
                duration,
                insertedDocumentIds,
                errors
        );
    }

    /**
     * Result of batch processing operation.
     */
    public record BatchProcessingResult(
            int insertedCount,
            int duplicateCount,
            int errorCount,
            long durationMs,
            List<UUID> insertedDocumentIds,
            List<ProcessingError> errors
    ) {
        public int getTotalProcessed() {
            return insertedCount + duplicateCount + errorCount;
        }

        public boolean hasErrors() {
            return errorCount > 0;
        }
    }

    /**
     * Processing error details.
     */
    public record ProcessingError(String filename, String errorMessage) {}
}
