package org.raddatz.familienarchiv.document;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.document.Document;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Bridges document upload paths to asynchronous thumbnail generation. Use
 * {@link #dispatchAfterCommit(UUID)} from inside {@code @Transactional} service methods —
 * it registers a post-commit hook so the async task only fires when the surrounding
 * transaction actually commits, and is silently skipped on rollback. Mirrors
 * {@link org.raddatz.familienarchiv.audit.AuditService#logAfterCommit}.
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ThumbnailAsyncRunner {

    private final DocumentRepository documentRepository;
    private final ThumbnailService thumbnailService;

    /** Per-document timeout for the whole generate() call — defense against corrupt PDFs. */
    private long generateTimeoutSeconds = 30L;

    /**
     * Registers a post-commit hook that triggers asynchronous thumbnail generation for the
     * given document. When no transaction is active the task is dispatched immediately.
     * Safe to call from inside {@code @Transactional} service methods.
     */
    public void dispatchAfterCommit(UUID documentId) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    generateAsync(documentId);
                }
            });
        } else {
            generateAsync(documentId);
        }
    }

    /**
     * Runs thumbnail generation on the {@code thumbnailExecutor} pool, wrapped in a watchdog
     * timeout so a hung PDFBox render cannot occupy a pool thread indefinitely. Never throws:
     * all errors and timeouts are logged and swallowed so upload paths are not affected.
     */
    @Async("thumbnailExecutor")
    public void generateAsync(UUID documentId) {
        Optional<Document> docOpt = documentRepository.findById(documentId);
        if (docOpt.isEmpty()) {
            log.warn("Thumbnail generation skipped: document not found id={}", documentId);
            return;
        }
        Document doc = docOpt.get();

        ExecutorService timeoutWorker = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "Thumbnail-Render-" + documentId);
            t.setDaemon(true);
            return t;
        });
        try {
            Future<ThumbnailService.Outcome> future = timeoutWorker.submit(
                    () -> thumbnailService.generate(doc));
            try {
                future.get(generateTimeoutSeconds, TimeUnit.SECONDS);
            } catch (TimeoutException e) {
                future.cancel(true);
                log.warn("Thumbnail generation timed out after {}s for doc={}",
                        generateTimeoutSeconds, documentId);
            } catch (Exception e) {
                log.warn("Thumbnail generation errored for doc={} reason={}",
                        documentId, e.getMessage());
            }
        } finally {
            timeoutWorker.shutdownNow();
        }
    }
}
