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.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

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.FailureCounters;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.history.DocumentHistoryRow;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryDetailsUseCase.HistoryDetailsResult;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;

/**
 * Tests für {@link DefaultHistoryDetailsUseCase}.
 * <p>
 * Prüft den Happy-Path (Stammsatz vorhanden), das leere-Optional-Verhalten
 * (kein Stammsatz), Null-Guards und Port-Fehler-Propagation.
 */
class DefaultHistoryDetailsUseCaseTest {

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

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

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

    @Test
    void loadDetails_nullFingerprint_throwsNPE() {
        DefaultHistoryDetailsUseCase useCase =
                new DefaultHistoryDetailsUseCase(emptyPort());
        assertThatNullPointerException()
                .isThrownBy(() -> useCase.loadDetails(null));
    }

    // -------------------------------------------------------------------------
    // Kein Stammsatz vorhanden
    // -------------------------------------------------------------------------

    @Test
    void loadDetails_noRecord_returnsEmpty() {
        DefaultHistoryDetailsUseCase useCase =
                new DefaultHistoryDetailsUseCase(emptyPort());

        Optional<HistoryDetailsResult> result = useCase.loadDetails(FP);

        assertThat(result).isEmpty();
    }

    // -------------------------------------------------------------------------
    // Happy path: Stammsatz vorhanden, Versuche vorhanden
    // -------------------------------------------------------------------------

    @Test
    void loadDetails_recordExists_returnsResultWithRecordAndAttempts() {
        DocumentRecord record = buildRecord(FP);
        ProcessingAttempt attempt = buildAttempt(FP);

        HistoryQueryPort port = new HistoryQueryPort() {
            @Override
            public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
                return Collections.emptyList();
            }

            @Override
            public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
                return Optional.of(record);
            }

            @Override
            public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
                return List.of(attempt);
            }
        };

        DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(port);
        Optional<HistoryDetailsResult> result = useCase.loadDetails(FP);

        assertThat(result).isPresent();
        assertThat(result.get().record()).isSameAs(record);
        assertThat(result.get().attempts()).containsExactly(attempt);
    }

    // -------------------------------------------------------------------------
    // Stammsatz vorhanden, keine Versuche
    // -------------------------------------------------------------------------

    @Test
    void loadDetails_recordExistsNoAttempts_returnsResultWithEmptyAttempts() {
        DocumentRecord record = buildRecord(FP);

        HistoryQueryPort port = new HistoryQueryPort() {
            @Override
            public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
                return Collections.emptyList();
            }

            @Override
            public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
                return Optional.of(record);
            }

            @Override
            public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
                return Collections.emptyList();
            }
        };

        DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(port);
        Optional<HistoryDetailsResult> result = useCase.loadDetails(FP);

        assertThat(result).isPresent();
        assertThat(result.get().attempts()).isEmpty();
    }

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

    @Test
    void loadDetails_portThrowsOnRecord_exceptionPropagated() {
        HistoryQueryPort failingPort = new HistoryQueryPort() {
            @Override
            public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
                return Collections.emptyList();
            }

            @Override
            public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
                throw new DocumentPersistenceException("Simulated DB error");
            }

            @Override
            public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
                return Collections.emptyList();
            }
        };
        DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(failingPort);

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

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

    private static HistoryQueryPort emptyPort() {
        return new HistoryQueryPort() {
            @Override
            public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
                return Collections.emptyList();
            }

            @Override
            public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
                return Optional.empty();
            }

            @Override
            public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
                return Collections.emptyList();
            }
        };
    }

    private static DocumentRecord buildRecord(DocumentFingerprint fp) {
        return new DocumentRecord(
                fp,
                new SourceDocumentLocator("/source"),
                "source.pdf",
                ProcessingStatus.SUCCESS,
                new FailureCounters(0, 0),
                null,
                Instant.now(),
                Instant.now(),
                Instant.now(),
                "/target",
                "2024-01-01 - Dokument.pdf");
    }

    private static ProcessingAttempt buildAttempt(DocumentFingerprint fp) {
        return ProcessingAttempt.withoutAiFields(
                fp,
                new de.gecheckt.pdf.umbenenner.domain.model.RunId(
                        java.util.UUID.randomUUID().toString()),
                1,
                Instant.now(),
                Instant.now(),
                ProcessingStatus.SUCCESS,
                null,
                null,
                false);
    }
}
