package at.procon.dip.migration.service;

import at.procon.dip.migration.config.LegacyTedBackfillProperties;
import at.procon.dip.migration.entity.LegacyTedMigrationCheckpoint;
import at.procon.dip.migration.entity.LegacyTedMigrationRun;
import at.procon.dip.migration.entity.LegacyTedMigrationRunStatus;
import at.procon.dip.migration.repository.LegacyTedMigrationCheckpointRepository;
import at.procon.dip.migration.repository.LegacyTedMigrationRunRepository;
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import at.procon.ted.repository.LegacyTedMigrationCursor;
import at.procon.ted.repository.ProcurementDocumentRepository;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
@RequiredArgsConstructor
@Slf4j
public class LegacyTedBackfillMigrationService {

    private final LegacyTedBackfillProperties properties;
    private final ProcurementDocumentRepository procurementDocumentRepository;
    private final LegacyTedMigrationRunRepository runRepository;
    private final LegacyTedMigrationCheckpointRepository checkpointRepository;
    private final LegacyTedBackfillWorker worker;

    public UUID runBackfill() {
        if (!properties.isEnabled()) {
            log.info("Legacy TED backfill is disabled");
            return null;
        }

        LegacyTedMigrationRun run = resolveRun();
        log.info("Starting legacy TED -> DOC/projection backfill run {} (batchSize={}, maxDocumentsPerRun={}, queueEmbeddings={}, migrateEmbeddings={})",
                run.getId(), run.getBatchSize(), run.getMaxDocumentsPerRun(), run.isQueueEmbeddings(), properties.isMigrateEmbeddings());

        long existingCheckpointCount = checkpointRepository.countByRun_Id(run.getId());
        int batchNumber = existingCheckpointCount >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) existingCheckpointCount;
        long processedInThisInvocation = 0;

        while (true) {
            int limit = effectiveBatchLimit(run, processedInThisInvocation);
            if (limit <= 0) {
                markStopped(run, "Stopped because maxDocumentsPerRun was reached");
                createCheckpoint(run, ++batchNumber, 0, "Invocation limit reached");
                return run.getId();
            }

            List<LegacyTedMigrationCursor> batch = loadNextBatch(run, limit);

            if (batch.isEmpty()) {
                markCompleted(run);
                createCheckpoint(run, ++batchNumber, 0, "Backfill completed - no more legacy TED documents after current cursor");
                return run.getId();
            }

            int processedInBatch = 0;
            for (LegacyTedMigrationCursor cursor : batch) {
                try {
                    LegacyTedBackfillWorker.BackfillOutcome outcome = worker.backfill(
                            cursor.getId(),
                            run.getImportBatchId(),
                            run.isQueueEmbeddings()
                    );
                    advanceRun(run, cursor, outcome);
                    processedInBatch++;
                    processedInThisInvocation++;
                } catch (RuntimeException ex) {
                    markFailed(run, cursor, ex);
                    createCheckpoint(run, ++batchNumber, processedInBatch,
                            "Failed at legacy document %s: %s".formatted(cursor.getId(), ex.getMessage()));
                    throw ex;
                }
            }
            createCheckpoint(run, ++batchNumber, processedInBatch,
                    "Processed %d legacy TED documents in batch".formatted(processedInBatch));
        }
    }

    protected List<LegacyTedMigrationCursor> loadNextBatch(LegacyTedMigrationRun run, int limit) {
        if (run.getLastLegacyCreatedAt() == null || run.getLastLegacyDocumentId() == null) {
            return procurementDocumentRepository.findFirstMigrationBatch(limit);
        }
        return procurementDocumentRepository.findNextMigrationBatch(
                run.getLastLegacyCreatedAt(),
                run.getLastLegacyDocumentId(),
                limit
        );
    }

    @Transactional
    protected LegacyTedMigrationRun resolveRun() {
        if (properties.isResumeLatestIncompleteRun()) {
            List<LegacyTedMigrationRun> resumable = runRepository.findLatestByStatuses(
                    EnumSet.of(LegacyTedMigrationRunStatus.RUNNING, LegacyTedMigrationRunStatus.STOPPED, LegacyTedMigrationRunStatus.FAILED),
                    PageRequest.of(0, 1)
            );
            if (!resumable.isEmpty()) {
                LegacyTedMigrationRun run = resumable.get(0);
                run.setStatus(LegacyTedMigrationRunStatus.RUNNING);
                run.setLastError(null);
                run.setCompletedAt(null);
                return runRepository.save(run);
            }
        }

        return runRepository.save(LegacyTedMigrationRun.builder()
                .status(LegacyTedMigrationRunStatus.RUNNING)
                .importBatchId(properties.getImportBatchId())
                .queueEmbeddings(properties.isQueueEmbeddings())
                .batchSize(Math.max(1, properties.getBatchSize()))
                .maxDocumentsPerRun(properties.getMaxDocumentsPerRun() > 0 ? properties.getMaxDocumentsPerRun() : null)
                .build());
    }

    @Transactional
    protected void advanceRun(LegacyTedMigrationRun run,
                              LegacyTedMigrationCursor cursor,
                              LegacyTedBackfillWorker.BackfillOutcome outcome) {
        run.setStatus(LegacyTedMigrationRunStatus.RUNNING);
        run.setProcessedCount(run.getProcessedCount() + 1);
        run.setSuccessCount(run.getSuccessCount() + 1);
        run.setLastLegacyCreatedAt(cursor.getCreatedAt() != null
                ? cursor.getCreatedAt().atOffset(ZoneOffset.UTC)
                : null);
        run.setLastLegacyDocumentId(cursor.getId());
        run.setLastDocDocumentId(outcome.documentId());
        run.setLastProjectionId(outcome.projectionId());
        run.setLastError(null);
        runRepository.save(run);
    }

    @Transactional
    protected void createCheckpoint(LegacyTedMigrationRun run, int batchNumber, int processedInBatch, String note) {
        checkpointRepository.save(LegacyTedMigrationCheckpoint.builder()
                .run(run)
                .batchNumber(batchNumber)
                .batchProcessedCount(processedInBatch)
                .cumulativeProcessedCount(run.getProcessedCount())
                .lastLegacyCreatedAt(run.getLastLegacyCreatedAt())
                .lastLegacyDocumentId(run.getLastLegacyDocumentId())
                .lastDocDocumentId(run.getLastDocDocumentId())
                .lastProjectionId(run.getLastProjectionId())
                .note(note)
                .build());
    }

    @Transactional
    protected void markCompleted(LegacyTedMigrationRun run) {
        run.setStatus(LegacyTedMigrationRunStatus.COMPLETED);
        run.setCompletedAt(OffsetDateTime.now());
        run.setLastError(null);
        runRepository.save(run);
        log.info("Legacy TED backfill run {} completed successfully - processed {} documents",
                run.getId(), run.getProcessedCount());
    }

    @Transactional
    protected void markStopped(LegacyTedMigrationRun run, String message) {
        run.setStatus(LegacyTedMigrationRunStatus.STOPPED);
        run.setLastError(message);
        runRepository.save(run);
        log.info("Legacy TED backfill run {} stopped after {} processed documents: {}",
                run.getId(), run.getProcessedCount(), message);
    }

    @Transactional
    protected void markFailed(LegacyTedMigrationRun run, LegacyTedMigrationCursor cursor, RuntimeException ex) {
        run.setStatus(LegacyTedMigrationRunStatus.FAILED);
        run.setFailedCount(run.getFailedCount() + 1);
        run.setLastError(ex.getMessage());
        runRepository.save(run);
        log.error("Legacy TED backfill run {} failed at legacy document {}: {}",
                run.getId(), cursor.getId(), ex.getMessage(), ex);
    }

    private int effectiveBatchLimit(LegacyTedMigrationRun run, long processedInThisInvocation) {
        long maxPerRun = run.getMaxDocumentsPerRun() != null ? run.getMaxDocumentsPerRun() : 0L;
        if (maxPerRun <= 0) {
            return Math.max(1, run.getBatchSize());
        }
        long remaining = maxPerRun - processedInThisInvocation;
        if (remaining <= 0) {
            return 0;
        }
        return (int) Math.max(1L, Math.min(run.getBatchSize(), remaining));
    }
}
