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

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

import java.time.Instant;
import java.util.Optional;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
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.DocumentRecordLookupResult;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalFinalFailure;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown;
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;

/**
 * Unit tests for {@link DefaultResolveHistoricalDocumentContextUseCase}.
 */
class DefaultResolveHistoricalDocumentContextUseCaseTest {

    private static final DocumentFingerprint FP = new DocumentFingerprint("a".repeat(64));
    private static final Instant NOW = Instant.parse("2026-01-15T10:30:00Z");

    @Test
    void constructor_withNullRepository_throws() {
        assertThatNullPointerException()
                .isThrownBy(() -> new DefaultResolveHistoricalDocumentContextUseCase(null))
                .withMessageContaining("documentRecordRepository");
    }

    @Test
    void resolve_withNullFingerprint_throws() {
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new DocumentUnknown()));
        assertThatNullPointerException()
                .isThrownBy(() -> useCase.resolveHistoricalDocumentContext(null))
                .withMessageContaining("fingerprint");
    }

    @Test
    void resolve_forSuccessRecord_returnsContextWithTargetFileNameAndSuccessInstant() {
        DocumentRecord record = buildRecord(ProcessingStatus.SUCCESS,
                "2026-01-01 - Rechnung.pdf", NOW, null);
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new DocumentTerminalSuccess(record)));

        Optional<HistoricalDocumentContext> result =
                useCase.resolveHistoricalDocumentContext(FP);

        assertThat(result).isPresent();
        assertThat(result.get().wasEverSuccessful()).isTrue();
        assertThat(result.get().lastTargetFileName()).contains("2026-01-01 - Rechnung.pdf");
        assertThat(result.get().lastSuccessInstant()).contains(NOW);
        assertThat(result.get().lastFailureInstant()).isEmpty();
    }

    @Test
    void resolve_forSuccessRecordWithNullTargetFileName_returnsContextWithEmptyFileName() {
        DocumentRecord record = buildRecord(ProcessingStatus.SUCCESS, null, NOW, null);
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new DocumentTerminalSuccess(record)));

        Optional<HistoricalDocumentContext> result =
                useCase.resolveHistoricalDocumentContext(FP);

        assertThat(result).isPresent();
        assertThat(result.get().wasEverSuccessful()).isTrue();
        assertThat(result.get().lastTargetFileName()).isEmpty();
        assertThat(result.get().lastSuccessInstant()).contains(NOW);
    }

    @Test
    void resolve_forFinalFailureRecord_returnsContextWithFailureInstant() {
        DocumentRecord record = buildRecord(ProcessingStatus.FAILED_FINAL, null, null, NOW);
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new DocumentTerminalFinalFailure(record)));

        Optional<HistoricalDocumentContext> result =
                useCase.resolveHistoricalDocumentContext(FP);

        assertThat(result).isPresent();
        assertThat(result.get().wasEverSuccessful()).isFalse();
        assertThat(result.get().lastFailureInstant()).contains(NOW);
        assertThat(result.get().lastTargetFileName()).isEmpty();
        assertThat(result.get().lastSuccessInstant()).isEmpty();
    }

    @Test
    void resolve_forUnknownDocument_returnsEmpty() {
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new DocumentUnknown()));

        assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
    }

    @Test
    void resolve_forProcessableDocument_returnsEmpty() {
        DocumentRecord record = buildRecord(ProcessingStatus.READY_FOR_AI, null, null, null);
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new DocumentKnownProcessable(record)));

        assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
    }

    @Test
    void resolve_forPersistenceLookupFailure_returnsEmpty() {
        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
                stubRepo(new PersistenceLookupTechnicalFailure("DB-Fehler", null)));

        assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
    }

    @Test
    void resolve_whenRepositoryThrows_returnsEmpty() {
        DocumentRecordRepository throwingRepo = new DocumentRecordRepository() {
            @Override
            public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fingerprint) {
                throw new DocumentPersistenceException("Verbindungsfehler", null);
            }
            @Override
            public void create(DocumentRecord record) {
                // intentionally empty
            }
            @Override
            public void update(DocumentRecord record) {
                // intentionally empty
            }
            @Override
            public void deleteByFingerprint(DocumentFingerprint fingerprint) {
                // intentionally empty
            }
        };

        var useCase = new DefaultResolveHistoricalDocumentContextUseCase(throwingRepo);

        assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
    }

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

    private static DocumentRecordRepository stubRepo(DocumentRecordLookupResult result) {
        return new DocumentRecordRepository() {
            @Override
            public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fingerprint) {
                return result;
            }
            @Override
            public void create(DocumentRecord record) {
                // intentionally empty
            }
            @Override
            public void update(DocumentRecord record) {
                // intentionally empty
            }
            @Override
            public void deleteByFingerprint(DocumentFingerprint fingerprint) {
                // intentionally empty
            }
        };
    }

    private static DocumentRecord buildRecord(
            ProcessingStatus status,
            String lastTargetFileName,
            Instant lastSuccessInstant,
            Instant lastFailureInstant) {
        return new DocumentRecord(
                FP,
                new SourceDocumentLocator("quell/pfad"),
                "original.pdf",
                status,
                FailureCounters.zero(),
                lastFailureInstant,
                lastSuccessInstant,
                Instant.now(),
                Instant.now(),
                lastTargetFileName != null ? "ziel/ordner" : null,
                lastTargetFileName);
    }
}
