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.ArrayList;
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.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.DefaultHistoryOverviewUseCase.HistoryOverviewResult;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;

/**
 * Tests für {@link DefaultHistoryOverviewUseCase}.
 * <p>
 * Prüft den Happy-Path, das LIMIT-501-Verhalten und Null-Guards.
 */
class DefaultHistoryOverviewUseCaseTest {

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

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

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

    @Test
    void loadOverview_nullQuery_throwsNPE() {
        DefaultHistoryOverviewUseCase useCase =
                new DefaultHistoryOverviewUseCase(emptyPort());
        assertThatNullPointerException()
                .isThrownBy(() -> useCase.loadOverview(null));
    }

    // -------------------------------------------------------------------------
    // Happy path: leer
    // -------------------------------------------------------------------------

    @Test
    void loadOverview_emptyDatabase_returnsEmptyResultWithoutMore() {
        DefaultHistoryOverviewUseCase useCase =
                new DefaultHistoryOverviewUseCase(emptyPort());

        HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());

        assertThat(result.rows()).isEmpty();
        assertThat(result.hasMore()).isFalse();
    }

    // -------------------------------------------------------------------------
    // Happy path: weniger als 500 Treffer
    // -------------------------------------------------------------------------

    @Test
    void loadOverview_fewerThan500Results_returnsAllRowsWithoutMore() {
        List<DocumentHistoryRow> rows = buildRows(10);
        DefaultHistoryOverviewUseCase useCase =
                new DefaultHistoryOverviewUseCase(fixedPort(rows));

        HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());

        assertThat(result.rows()).hasSize(10);
        assertThat(result.hasMore()).isFalse();
    }

    // -------------------------------------------------------------------------
    // LIMIT-501-Technik
    // -------------------------------------------------------------------------

    @Test
    void loadOverview_exactly500Results_returnsAllWithoutMore() {
        List<DocumentHistoryRow> rows = buildRows(500);
        DefaultHistoryOverviewUseCase useCase =
                new DefaultHistoryOverviewUseCase(fixedPort(rows));

        HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());

        assertThat(result.rows()).hasSize(500);
        assertThat(result.hasMore()).isFalse();
    }

    @Test
    void loadOverview_moreThan500Results_returns500RowsWithHasMore() {
        List<DocumentHistoryRow> rows = buildRows(501);
        DefaultHistoryOverviewUseCase useCase =
                new DefaultHistoryOverviewUseCase(fixedPort(rows));

        HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());

        assertThat(result.rows()).hasSize(500);
        assertThat(result.hasMore()).isTrue();
    }

    @Test
    void loadOverview_resultListIsImmutable() {
        List<DocumentHistoryRow> rows = buildRows(3);
        DefaultHistoryOverviewUseCase useCase =
                new DefaultHistoryOverviewUseCase(fixedPort(rows));

        HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());

        assertThatThrownBy(() -> result.rows().add(buildRow("0".repeat(64))))
                .isInstanceOf(UnsupportedOperationException.class);
    }

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

    @Test
    void loadOverview_portThrows_exceptionPropagated() {
        HistoryQueryPort failingPort = new HistoryQueryPort() {
            @Override
            public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
                throw new DocumentPersistenceException("Simulated DB error");
            }

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

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

        assertThatThrownBy(() -> useCase.loadOverview(HistoryQuery.unfiltered()))
                .isInstanceOf(DocumentPersistenceException.class)
                .hasMessageContaining("Simulated DB error");
    }

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

    private static HistoryQueryPort emptyPort() {
        return fixedPort(Collections.emptyList());
    }

    private static HistoryQueryPort fixedPort(List<DocumentHistoryRow> rows) {
        return new HistoryQueryPort() {
            @Override
            public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
                return new ArrayList<>(rows);
            }

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

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

    private static List<DocumentHistoryRow> buildRows(int count) {
        List<DocumentHistoryRow> result = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            String hex = String.format("%064x", i);
            result.add(buildRow(hex));
        }
        return result;
    }

    private static DocumentHistoryRow buildRow(String fpHex) {
        return new DocumentHistoryRow(
                new DocumentFingerprint(fpHex),
                ProcessingStatus.SUCCESS,
                "source.pdf",
                "2024-01-01 - Dokument.pdf",
                "/source",
                Instant.now(),
                1L);
    }
}
