package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;

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

import java.nio.file.Path;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

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

/**
 * Tests for {@link SqliteDocumentRecordRepositoryAdapter}.
 */
class SqliteDocumentRecordRepositoryAdapterTest {

    private SqliteDocumentRecordRepositoryAdapter repository;
    private String jdbcUrl;

    @TempDir
    Path tempDir;

    @BeforeEach
    void setUp() {
        Path dbFile = tempDir.resolve("test.db");
        jdbcUrl = "jdbc:sqlite:" + dbFile.toAbsolutePath();
        
        // Initialize schema first
        SqliteSchemaInitializationAdapter schemaInitializer = 
                new SqliteSchemaInitializationAdapter(jdbcUrl);
        schemaInitializer.initializeSchema();
        
        repository = new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
    }

    @Test
    void findByFingerprint_shouldReturnDocumentUnknown_whenRecordDoesNotExist() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "0000000000000000000000000000000000000000000000000000000000000000");

        // When
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentUnknown.class);
    }

    @Test
    void create_and_findByFingerprint_shouldWorkForNewRecord() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "1111111111111111111111111111111111111111111111111111111111111111");
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/path/to/document.pdf"),
                "document.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                null,
                null
        );

        // When
        repository.create(record);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentKnownProcessable known = (DocumentKnownProcessable) result;
        DocumentRecord foundRecord = known.record();
        
        assertThat(foundRecord.fingerprint()).isEqualTo(fingerprint);
        assertThat(foundRecord.lastKnownSourceLocator().value()).isEqualTo("/path/to/document.pdf");
        assertThat(foundRecord.lastKnownSourceFileName()).isEqualTo("document.pdf");
        assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.PROCESSING);
        assertThat(foundRecord.failureCounters()).isEqualTo(FailureCounters.zero());
        assertThat(foundRecord.lastFailureInstant()).isNull();
        assertThat(foundRecord.lastSuccessInstant()).isNull();
        assertThat(foundRecord.createdAt()).isEqualTo(record.createdAt());
        assertThat(foundRecord.updatedAt()).isEqualTo(record.updatedAt());
    }

    @Test
    void update_shouldModifyExistingRecord() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "2222222222222222222222222222222222222222222222222222222222222222");
        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/initial/path.pdf"),
                "initial.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MICROS),
                Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MICROS),
                null,
                null
        );
        
        repository.create(initialRecord);
        
        // Updated record
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord updatedRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/updated/path.pdf"),
                "updated.pdf",
                ProcessingStatus.SUCCESS,
                new FailureCounters(0, 0),
                null,
                now,
                initialRecord.createdAt(),
                now,
                null,
                null
        );

        // When
        repository.update(updatedRecord);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentTerminalSuccess.class);
        DocumentTerminalSuccess success = (DocumentTerminalSuccess) result;
        DocumentRecord foundRecord = success.record();
        
        assertThat(foundRecord.lastKnownSourceLocator().value()).isEqualTo("/updated/path.pdf");
        assertThat(foundRecord.lastKnownSourceFileName()).isEqualTo("updated.pdf");
        assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.SUCCESS);
        assertThat(foundRecord.lastSuccessInstant()).isEqualTo(now);
        assertThat(foundRecord.updatedAt()).isEqualTo(now);
    }

    @Test
    void create_shouldThrowException_whenFingerprintAlreadyExists() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "3333333333333333333333333333333333333333333333333333333333333333");
        DocumentRecord record1 = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/path1.pdf"),
                "doc1.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                null,
                null
        );
        
        repository.create(record1);

        DocumentRecord record2 = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/path2.pdf"),
                "doc2.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                null,
                null
        );

        // When / Then
        assertThatThrownBy(() -> repository.create(record2))
                .isInstanceOf(DocumentPersistenceException.class);
    }

    @Test
    void update_shouldThrowException_whenFingerprintDoesNotExist() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "4444444444444444444444444444444444444444444444444444444444444444");
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/nonexistent.pdf"),
                "nonexistent.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                null,
                null
        );

        // When / Then
        assertThatThrownBy(() -> repository.update(record))
                .isInstanceOf(DocumentPersistenceException.class)
                .hasMessageContaining("Expected to update 1 row but affected 0 rows");
    }

    @Test
    void findByFingerprint_shouldReturnDocumentTerminalFinalFailure_whenStatusIsFailedFinal() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "5555555555555555555555555555555555555555555555555555555555555555");
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        // Create initially as PROCESSING
        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/path/to/document.pdf"),
                "document.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                now.minusSeconds(120),
                now.minusSeconds(120),
                null,
                null
        );
        repository.create(initialRecord);

        // Update to FAILED_FINAL (second content error: count=2)
        Instant failureInstant = now.minusSeconds(60);
        DocumentRecord failedFinalRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/path/to/document.pdf"),
                "document.pdf",
                ProcessingStatus.FAILED_FINAL,
                new FailureCounters(2, 0),
                failureInstant,
                null,
                now.minusSeconds(120),
                failureInstant,
                null,
                null
        );
        repository.update(failedFinalRecord);

        // When
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentTerminalFinalFailure.class);
        DocumentTerminalFinalFailure terminalFailure = (DocumentTerminalFinalFailure) result;
        DocumentRecord foundRecord = terminalFailure.record();
        assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.FAILED_FINAL);
        assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(2);
        assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(0);
        assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
        assertThat(foundRecord.lastSuccessInstant()).isNull();
    }

    @Test
    void update_shouldPersistNonZeroFailureCountersAndFailureInstant() {
        // Given: create a new document as PROCESSING
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "6666666666666666666666666666666666666666666666666666666666666666");
        Instant createdAt = Instant.now().minusSeconds(120).truncatedTo(ChronoUnit.MICROS);
        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/invoice.pdf"),
                "invoice.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                createdAt,
                createdAt,
                null,
                null
        );
        repository.create(initialRecord);

        // Update to FAILED_RETRYABLE with content_error_count=1, transient_error_count=0
        Instant failureInstant = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord failedRetryableRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/invoice.pdf"),
                "invoice.pdf",
                ProcessingStatus.FAILED_RETRYABLE,
                new FailureCounters(1, 0),
                failureInstant,
                null,
                createdAt,
                failureInstant,
                null,
                null
        );

        // When
        repository.update(failedRetryableRecord);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then: lookup returns DocumentKnownProcessable (FAILED_RETRYABLE is not terminal)
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentKnownProcessable known = (DocumentKnownProcessable) result;
        DocumentRecord foundRecord = known.record();
        assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.FAILED_RETRYABLE);
        assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(1);
        assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(0);
        assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
        assertThat(foundRecord.lastSuccessInstant()).isNull();
        assertThat(foundRecord.createdAt()).isEqualTo(createdAt);
        assertThat(foundRecord.updatedAt()).isEqualTo(failureInstant);
    }

    @Test
    void update_shouldTransitionFromFailedRetryableToFailedFinalWithIncrementedCounter() {
        // Given: create as FAILED_RETRYABLE with content_error_count=1 (first content error already recorded)
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "7777777777777777777777777777777777777777777777777777777777777777");
        Instant createdAt = Instant.now().minusSeconds(300).truncatedTo(ChronoUnit.MICROS);
        Instant firstFailureAt = Instant.now().minusSeconds(120).truncatedTo(ChronoUnit.MICROS);

        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/report.pdf"),
                "report.pdf",
                ProcessingStatus.FAILED_RETRYABLE,
                new FailureCounters(1, 0),
                firstFailureAt,
                null,
                createdAt,
                firstFailureAt,
                null,
                null
        );
        repository.create(initialRecord);

        // Second content error: update to FAILED_FINAL with content_error_count=2
        Instant secondFailureAt = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord failedFinalRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/report.pdf"),
                "report.pdf",
                ProcessingStatus.FAILED_FINAL,
                new FailureCounters(2, 0),
                secondFailureAt,
                null,
                createdAt,
                secondFailureAt,
                null,
                null
        );

        // When
        repository.update(failedFinalRecord);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then: terminal final failure
        assertThat(result).isInstanceOf(DocumentTerminalFinalFailure.class);
        DocumentTerminalFinalFailure terminalFailure = (DocumentTerminalFinalFailure) result;
        DocumentRecord foundRecord = terminalFailure.record();
        assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.FAILED_FINAL);
        assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(2);
        assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(0);
        assertThat(foundRecord.lastFailureInstant()).isEqualTo(secondFailureAt);
        assertThat(foundRecord.lastSuccessInstant()).isNull();
    }

    @Test
    void update_shouldPersistTransientErrorCounter() {
        // Given: create as PROCESSING
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "8888888888888888888888888888888888888888888888888888888888888888");
        Instant createdAt = Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MICROS);
        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/scan.pdf"),
                "scan.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                createdAt,
                createdAt,
                null,
                null
        );
        repository.create(initialRecord);

        // Update to FAILED_RETRYABLE with transient_error_count=3 (technical errors)
        Instant failureInstant = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord transientFailureRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/scan.pdf"),
                "scan.pdf",
                ProcessingStatus.FAILED_RETRYABLE,
                new FailureCounters(0, 3),
                failureInstant,
                null,
                createdAt,
                failureInstant,
                null,
                null
        );

        // When
        repository.update(transientFailureRecord);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentKnownProcessable known = (DocumentKnownProcessable) result;
        DocumentRecord foundRecord = known.record();
        assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(0);
        assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(3);
        assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
    }

    @Test
    void findByFingerprint_shouldThrowNullPointerException_whenFingerprintIsNull() {
        // Given
        DocumentFingerprint nullFingerprint = null;

        // When / Then
        assertThatThrownBy(() -> repository.findByFingerprint(nullFingerprint))
                .isInstanceOf(NullPointerException.class);
    }

    @Test
    void create_shouldThrowNullPointerException_whenRecordIsNull() {
        // When / Then
        assertThatThrownBy(() -> repository.create(null))
                .isInstanceOf(NullPointerException.class);
    }

    @Test
    void update_shouldThrowNullPointerException_whenRecordIsNull() {
        // When / Then
        assertThatThrownBy(() -> repository.update(null))
                .isInstanceOf(NullPointerException.class);
    }

    @Test
    void findByFingerprint_shouldReturnDocumentKnownProcessable_whenStatusIsSkippedAlreadyProcessed() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "9999999999999999999999999999999999999999999999999999999999999999");
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/skipped.pdf"),
                "skipped.pdf",
                ProcessingStatus.SKIPPED_ALREADY_PROCESSED,
                FailureCounters.zero(),
                null,
                null,
                now,
                now,
                null,
                null
        );
        repository.create(record);

        // When
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then: SKIPPED_ALREADY_PROCESSED is not terminal, should be DocumentKnownProcessable
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentKnownProcessable known = (DocumentKnownProcessable) result;
        assertThat(known.record().overallStatus()).isEqualTo(ProcessingStatus.SKIPPED_ALREADY_PROCESSED);
    }

    @Test
    void findByFingerprint_shouldReturnDocumentKnownProcessable_whenStatusIsSkippedFinalFailure() {
        // Given
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/final-skipped.pdf"),
                "final-skipped.pdf",
                ProcessingStatus.SKIPPED_FINAL_FAILURE,
                new FailureCounters(2, 0),
                now.minusSeconds(60),
                null,
                now,
                now,
                null,
                null
        );
        repository.create(record);

        // When
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then: SKIPPED_FINAL_FAILURE is not terminal, should be DocumentKnownProcessable
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentKnownProcessable known = (DocumentKnownProcessable) result;
        assertThat(known.record().overallStatus()).isEqualTo(ProcessingStatus.SKIPPED_FINAL_FAILURE);
    }

    @Test
    void create_and_update_shouldPreserveNullTimestamps() {
        // Given: create with null timestamps
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/no-timestamps.pdf"),
                "no-timestamps.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,  // lastFailureInstant is null
                null,  // lastSuccessInstant is null
                now,
                now,
                null,
                null
        );
        repository.create(record);

        // When
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentKnownProcessable known = (DocumentKnownProcessable) result;
        assertThat(known.record().lastFailureInstant()).isNull();
        assertThat(known.record().lastSuccessInstant()).isNull();
    }

    @Test
    void create_and_update_shouldPersistAndReadTargetPathAndTargetFileName() {
        // Given: create a record with null target fields initially
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/doc.pdf"),
                "doc.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null, null,
                now, now,
                null, null
        );
        repository.create(initialRecord);

        // Update with target path and filename
        DocumentRecord successRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/doc.pdf"),
                "doc.pdf",
                ProcessingStatus.SUCCESS,
                FailureCounters.zero(),
                null, now,
                now, now,
                "/target/folder",
                "2026-01-15 - Rechnung.pdf"
        );

        // When
        repository.update(successRecord);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentTerminalSuccess.class);
        DocumentRecord found = ((DocumentTerminalSuccess) result).record();
        assertThat(found.lastTargetPath()).isEqualTo("/target/folder");
        assertThat(found.lastTargetFileName()).isEqualTo("2026-01-15 - Rechnung.pdf");
    }

    @Test
    void update_shouldPersistNullTargetFields_whenNotYetCopied() {
        // Given: a record with null target path and filename (not yet in SUCCESS)
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/pending.pdf"),
                "pending.pdf",
                ProcessingStatus.FAILED_RETRYABLE,
                new FailureCounters(0, 1),
                now, null,
                now, now,
                null, null
        );
        repository.create(record);

        // When
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then
        assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
        DocumentRecord found = ((DocumentKnownProcessable) result).record();
        assertThat(found.lastTargetPath()).isNull();
        assertThat(found.lastTargetFileName()).isNull();
    }

    @Test
    void update_shouldPreserveCreatedAtTimestamp() {
        // Given: create with specific createdAt
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc");
        Instant createdAt = Instant.now().minusSeconds(1000).truncatedTo(ChronoUnit.MICROS);
        Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);

        DocumentRecord initialRecord = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/test.pdf"),
                "test.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                createdAt,  // Much older createdAt
                createdAt,
                null,
                null
        );
        repository.create(initialRecord);

        // Update with new timestamps
        DocumentRecord updated = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/source/test.pdf"),
                "test.pdf",
                ProcessingStatus.SUCCESS,
                FailureCounters.zero(),
                null,
                now,
                createdAt,  // createdAt should remain unchanged
                now,
                null,
                null
        );

        // When
        repository.update(updated);
        DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);

        // Then: createdAt should be preserved
        assertThat(result).isInstanceOf(DocumentTerminalSuccess.class);
        DocumentTerminalSuccess success = (DocumentTerminalSuccess) result;
        assertThat(success.record().createdAt()).isEqualTo(createdAt);
        assertThat(success.record().updatedAt()).isEqualTo(now);
    }

    // -------------------------------------------------------------------------
    // deleteByFingerprint
    // -------------------------------------------------------------------------

    @Test
    void deleteByFingerprint_removesExistingRecord() {
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        DocumentRecord record = new DocumentRecord(
                fingerprint,
                new SourceDocumentLocator("/path/del.pdf"),
                "del.pdf",
                ProcessingStatus.PROCESSING,
                FailureCounters.zero(),
                null,
                null,
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                Instant.now().truncatedTo(ChronoUnit.MICROS),
                null,
                null
        );
        repository.create(record);
        assertThat(repository.findByFingerprint(fingerprint))
                .isNotInstanceOf(DocumentUnknown.class);

        repository.deleteByFingerprint(fingerprint);

        assertThat(repository.findByFingerprint(fingerprint))
                .isInstanceOf(DocumentUnknown.class);
    }

    @Test
    void deleteByFingerprint_isIdempotentWhenRecordAbsent() {
        DocumentFingerprint fingerprint = new DocumentFingerprint(
                "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");

        // Must not throw even if the record does not exist
        repository.deleteByFingerprint(fingerprint);

        assertThat(repository.findByFingerprint(fingerprint))
                .isInstanceOf(DocumentUnknown.class);
    }
}