package org.raddatz.familienarchiv.document;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.document.Document;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

class ThumbnailBackfillServiceTest {

    private DocumentService documentService;
    private ThumbnailService thumbnailService;
    private ThumbnailBackfillService backfillService;

    @BeforeEach
    void setUp() {
        documentService = mock(DocumentService.class);
        thumbnailService = mock(ThumbnailService.class);
        backfillService = new ThumbnailBackfillService(documentService, thumbnailService);
    }

    @Test
    void initialStatus_isIdle() {
        ThumbnailBackfillService.BackfillStatus status = backfillService.getStatus();

        assertThat(status.state()).isEqualTo(ThumbnailBackfillService.State.IDLE);
        assertThat(status.total()).isZero();
        assertThat(status.processed()).isZero();
        assertThat(status.startedAt()).isNull();
    }

    @Test
    void runBackfillAsync_processesAllDocumentsAndFinishesDone() {
        Document a = doc();
        Document b = doc();
        Document c = doc();
        when(documentService.findForThumbnailBackfill())
                .thenReturn(List.of(a, b, c));
        when(thumbnailService.generate(any())).thenReturn(ThumbnailService.Outcome.SUCCESS);

        backfillService.runBackfillAsync();

        ThumbnailBackfillService.BackfillStatus status = backfillService.getStatus();
        assertThat(status.state()).isEqualTo(ThumbnailBackfillService.State.DONE);
        assertThat(status.total()).isEqualTo(3);
        assertThat(status.processed()).isEqualTo(3);
        assertThat(status.skipped()).isZero();
        assertThat(status.failed()).isZero();
        verify(thumbnailService, times(3)).generate(any());
    }

    @Test
    void runBackfillAsync_countsSkippedSeparately() {
        Document a = doc();
        Document b = doc();
        when(documentService.findForThumbnailBackfill())
                .thenReturn(List.of(a, b));
        when(thumbnailService.generate(a)).thenReturn(ThumbnailService.Outcome.SUCCESS);
        when(thumbnailService.generate(b)).thenReturn(ThumbnailService.Outcome.SKIPPED);

        backfillService.runBackfillAsync();

        ThumbnailBackfillService.BackfillStatus status = backfillService.getStatus();
        assertThat(status.state()).isEqualTo(ThumbnailBackfillService.State.DONE);
        assertThat(status.processed()).isEqualTo(1);
        assertThat(status.skipped()).isEqualTo(1);
        assertThat(status.failed()).isZero();
    }

    @Test
    void runBackfillAsync_continuesAfterFailureAndCountsIt() {
        Document a = doc();
        Document b = doc();
        Document c = doc();
        when(documentService.findForThumbnailBackfill())
                .thenReturn(List.of(a, b, c));
        when(thumbnailService.generate(a)).thenReturn(ThumbnailService.Outcome.SUCCESS);
        when(thumbnailService.generate(b)).thenReturn(ThumbnailService.Outcome.FAILED);
        when(thumbnailService.generate(c)).thenReturn(ThumbnailService.Outcome.SUCCESS);

        backfillService.runBackfillAsync();

        ThumbnailBackfillService.BackfillStatus status = backfillService.getStatus();
        assertThat(status.state()).isEqualTo(ThumbnailBackfillService.State.DONE);
        assertThat(status.processed()).isEqualTo(2);
        assertThat(status.failed()).isEqualTo(1);
        verify(thumbnailService, times(3)).generate(any());
    }

    @Test
    void runBackfillAsync_continuesWhenServiceThrowsUnexpectedException() {
        Document a = doc();
        Document b = doc();
        when(documentService.findForThumbnailBackfill())
                .thenReturn(List.of(a, b));
        when(thumbnailService.generate(a)).thenThrow(new RuntimeException("boom"));
        when(thumbnailService.generate(b)).thenReturn(ThumbnailService.Outcome.SUCCESS);

        backfillService.runBackfillAsync();

        ThumbnailBackfillService.BackfillStatus status = backfillService.getStatus();
        assertThat(status.state()).isEqualTo(ThumbnailBackfillService.State.DONE);
        assertThat(status.processed()).isEqualTo(1);
        assertThat(status.failed()).isEqualTo(1);
    }

    @Test
    void runBackfillAsync_rejectsConcurrentStart() {
        // Force state=RUNNING via reflection
        ThumbnailBackfillService.BackfillStatus running = new ThumbnailBackfillService.BackfillStatus(
                ThumbnailBackfillService.State.RUNNING, "running", 10, 5, 0, 0, LocalDateTime.now());
        ReflectionTestUtils.setField(backfillService, "currentStatus", running);

        assertThatThrownBy(() -> backfillService.runBackfillAsync())
                .isInstanceOf(DomainException.class)
                .satisfies(ex -> assertThat(((DomainException) ex).getCode())
                        .isEqualTo(ErrorCode.THUMBNAIL_BACKFILL_ALREADY_RUNNING));
    }

    @Test
    void runBackfillAsync_setsStartedAtAndMessage() {
        when(documentService.findForThumbnailBackfill())
                .thenReturn(List.of(doc()));
        when(thumbnailService.generate(any())).thenReturn(ThumbnailService.Outcome.SUCCESS);

        LocalDateTime before = LocalDateTime.now().minusSeconds(1);
        backfillService.runBackfillAsync();

        ThumbnailBackfillService.BackfillStatus status = backfillService.getStatus();
        assertThat(status.startedAt()).isAfter(before);
        assertThat(status.message()).isNotBlank();
    }

    private Document doc() {
        return Document.builder()
                .id(UUID.randomUUID())
                .title("t")
                .originalFilename("f.pdf")
                .filePath("documents/f.pdf")
                .contentType("application/pdf")
                .build();
    }
}
