package de.gecheckt.pdf.umbenenner.application.usecase;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;

/**
 * Tests für {@link DefaultDeleteDocumentHistoryUseCase}.
 * <p>
 * Prüft, dass ausschließlich {@code resetDocumentByFingerprint} aufgerufen wird
 * (vollständige Löschung inklusive Versuchen, FK-sicher), Null-Guards greifen
 * und Port-Fehler propagiert werden.
 */
class DefaultDeleteDocumentHistoryUseCaseTest {

    private static final DocumentFingerprint FP =
            new DocumentFingerprint("b".repeat(64));

    // -------------------------------------------------------------------------
    // Null-Guards
    // -------------------------------------------------------------------------

    @Test
    void constructor_nullPort_throwsNPE() {
        assertThatNullPointerException()
                .isThrownBy(() -> new DefaultDeleteDocumentHistoryUseCase(null));
    }

    @Test
    void deleteHistory_nullFingerprint_throwsNPE() {
        DefaultDeleteDocumentHistoryUseCase useCase =
                new DefaultDeleteDocumentHistoryUseCase(noOpPort());
        assertThatNullPointerException()
                .isThrownBy(() -> useCase.deleteHistory(null));
    }

    // -------------------------------------------------------------------------
    // Happy path: vollständige Löschung
    // -------------------------------------------------------------------------

    @Test
    void deleteHistory_callsResetDocumentByFingerprint() {
        RecordingTransactionOperations ops = new RecordingTransactionOperations();
        UnitOfWorkPort port = operations -> operations.accept(ops);

        DefaultDeleteDocumentHistoryUseCase useCase =
                new DefaultDeleteDocumentHistoryUseCase(port);
        useCase.deleteHistory(FP);

        assertThat(ops.resetByFingerprintFingerprints)
                .containsExactly(FP);
    }

    @Test
    void deleteHistory_doesNotCallResetDocumentStatusForRetry() {
        RecordingTransactionOperations ops = new RecordingTransactionOperations();
        UnitOfWorkPort port = operations -> operations.accept(ops);

        DefaultDeleteDocumentHistoryUseCase useCase =
                new DefaultDeleteDocumentHistoryUseCase(port);
        useCase.deleteHistory(FP);

        assertThat(ops.resetStatusForRetryFingerprints).isEmpty();
    }

    // -------------------------------------------------------------------------
    // Port-Fehler wird propagiert
    // -------------------------------------------------------------------------

    @Test
    void deleteHistory_portThrows_exceptionPropagated() {
        UnitOfWorkPort failingPort = operations ->
                operations.accept(new UnitOfWorkPort.TransactionOperations() {
                    @Override
                    public void saveProcessingAttempt(ProcessingAttempt attempt) {
                        // intentionally empty
                    }
                    @Override
                    public void createDocumentRecord(DocumentRecord record) {
                        // intentionally empty
                    }
                    @Override
                    public void updateDocumentRecord(DocumentRecord record) {
                        // intentionally empty
                    }
                    @Override
                    public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
                        throw new DocumentPersistenceException("Simulated DB error");
                    }
                    @Override
                    public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
                        // intentionally empty
                    }
                });

        DefaultDeleteDocumentHistoryUseCase useCase =
                new DefaultDeleteDocumentHistoryUseCase(failingPort);

        assertThatThrownBy(() -> useCase.deleteHistory(FP))
                .isInstanceOf(DocumentPersistenceException.class)
                .hasMessageContaining("Simulated DB error");
    }

    // -------------------------------------------------------------------------
    // Hilfsmethoden
    // -------------------------------------------------------------------------

    private static UnitOfWorkPort noOpPort() {
        return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
            @Override public void saveProcessingAttempt(ProcessingAttempt a) {
                // intentionally empty
            }
            @Override public void createDocumentRecord(DocumentRecord r) {
                // intentionally empty
            }
            @Override public void updateDocumentRecord(DocumentRecord r) {
                // intentionally empty
            }
            @Override public void resetDocumentByFingerprint(DocumentFingerprint fp) {
                // intentionally empty
            }
            @Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) {
                // intentionally empty
            }
        });
    }

    /**
     * Zeichnet {@code resetDocumentByFingerprint}- und {@code resetDocumentStatusForRetry}-Aufrufe auf.
     */
    private static class RecordingTransactionOperations
            implements UnitOfWorkPort.TransactionOperations {

        final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
        final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();

        @Override public void saveProcessingAttempt(ProcessingAttempt a) {
            // intentionally empty
        }
        @Override public void createDocumentRecord(DocumentRecord r) {
            // intentionally empty
        }
        @Override public void updateDocumentRecord(DocumentRecord r) {
            // intentionally empty
        }

        @Override
        public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
            resetByFingerprintFingerprints.add(fingerprint);
        }

        @Override
        public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
            resetStatusForRetryFingerprints.add(fingerprint);
        }
    }
}
