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 DefaultHistoryResetDocumentStatusUseCase}.
 * <p>
 * Prüft, dass ausschließlich {@code resetDocumentStatusForRetry} aufgerufen wird
 * (nicht {@code resetDocumentByFingerprint}), Null-Guards greifen und
 * Port-Fehler propagiert werden.
 */
class DefaultHistoryResetDocumentStatusUseCaseTest {

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

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

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

    @Test
    void resetStatus_nullFingerprint_throwsNPE() {
        DefaultHistoryResetDocumentStatusUseCase useCase =
                new DefaultHistoryResetDocumentStatusUseCase(noOpPort());
        assertThatNullPointerException()
                .isThrownBy(() -> useCase.resetStatus(null));
    }

    // -------------------------------------------------------------------------
    // Happy path: feldgenauer Reset
    // -------------------------------------------------------------------------

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

        DefaultHistoryResetDocumentStatusUseCase useCase =
                new DefaultHistoryResetDocumentStatusUseCase(port);
        useCase.resetStatus(FP);

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

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

        DefaultHistoryResetDocumentStatusUseCase useCase =
                new DefaultHistoryResetDocumentStatusUseCase(port);
        useCase.resetStatus(FP);

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

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

    @Test
    void resetStatus_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) {
                        // intentionally empty
                    }
                    @Override
                    public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
                        throw new DocumentPersistenceException("Simulated DB error");
                    }
                });

        DefaultHistoryResetDocumentStatusUseCase useCase =
                new DefaultHistoryResetDocumentStatusUseCase(failingPort);

        assertThatThrownBy(() -> useCase.resetStatus(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 resetDocumentStatusForRetry}- und {@code resetDocumentByFingerprint}-Aufrufe auf.
     */
    private static class RecordingTransactionOperations
            implements UnitOfWorkPort.TransactionOperations {

        final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
        final List<DocumentFingerprint> resetByFingerprintFingerprints = 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);
        }
    }
}
