package org.raddatz.familienarchiv.ocr;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.raddatz.familienarchiv.ocr.OcrStatusDTO;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.DocumentStatus;
import org.raddatz.familienarchiv.ocr.OcrJobDocumentRepository;
import org.raddatz.familienarchiv.ocr.OcrJobRepository;

import org.raddatz.familienarchiv.document.DocumentService;

import java.util.Optional;
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.*;
import static org.springframework.http.HttpStatus.*;

@ExtendWith(MockitoExtension.class)
class OcrServiceTest {

    @Mock OcrHealthClient ocrHealthClient;
    @Mock DocumentService documentService;
    @Mock OcrJobRepository ocrJobRepository;
    @Mock OcrJobDocumentRepository ocrJobDocumentRepository;
    @Mock OcrAsyncRunner ocrAsyncRunner;

    @InjectMocks OcrService ocrService;

    // ─── getJob ──────────────────────────────────────────────────────────────────

    @Test
    void getJob_returnsJob_whenFound() {
        UUID jobId = UUID.randomUUID();
        OcrJob job = OcrJob.builder().id(jobId).status(OcrJobStatus.RUNNING).build();
        when(ocrJobRepository.findById(jobId)).thenReturn(Optional.of(job));

        OcrJob result = ocrService.getJob(jobId);

        assertThat(result).isEqualTo(job);
    }

    @Test
    void getJob_throwsNotFound_whenJobDoesNotExist() {
        UUID jobId = UUID.randomUUID();
        when(ocrJobRepository.findById(jobId)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> ocrService.getJob(jobId))
                .isInstanceOf(DomainException.class)
                .satisfies(e -> {
                    DomainException de = (DomainException) e;
                    assertThat(de.getStatus()).isEqualTo(NOT_FOUND);
                    assertThat(de.getCode()).isEqualTo(ErrorCode.OCR_JOB_NOT_FOUND);
                });
    }

    // ─── getDocumentOcrStatus ───────────────────────────────────────────────────

    @Test
    void getDocumentOcrStatus_returnsNone_whenNoActiveJob() {
        UUID docId = UUID.randomUUID();
        when(ocrJobDocumentRepository.findFirstByDocumentIdAndStatusIn(any(), any()))
                .thenReturn(Optional.empty());

        OcrStatusDTO result = ocrService.getDocumentOcrStatus(docId);

        assertThat(result.getStatus()).isEqualTo("NONE");
        assertThat(result.getJobId()).isNull();
    }

    @Test
    void getDocumentOcrStatus_returnsActiveStatus_whenJobExists() {
        UUID docId = UUID.randomUUID();
        UUID jobId = UUID.randomUUID();
        OcrJobDocument jobDoc = OcrJobDocument.builder()
                .jobId(jobId).documentId(docId)
                .status(OcrDocumentStatus.RUNNING)
                .currentPage(2).totalPages(5)
                .build();
        when(ocrJobDocumentRepository.findFirstByDocumentIdAndStatusIn(any(), any()))
                .thenReturn(Optional.of(jobDoc));

        OcrStatusDTO result = ocrService.getDocumentOcrStatus(docId);

        assertThat(result.getStatus()).isEqualTo("RUNNING");
        assertThat(result.getJobId()).isEqualTo(jobId);
        assertThat(result.getCurrentPage()).isEqualTo(2);
        assertThat(result.getTotalPages()).isEqualTo(5);
    }

    // ─── startOcr ───────────────────────────────────────────────────────────────

    @Test
    void startOcr_throwsBadRequest_whenDocumentIsPlaceholder() {
        UUID docId = UUID.randomUUID();
        Document doc = Document.builder().id(docId).status(DocumentStatus.PLACEHOLDER).build();
        when(documentService.getDocumentById(docId)).thenReturn(doc);

        assertThatThrownBy(() -> ocrService.startOcr(docId, null, UUID.randomUUID()))
                .isInstanceOf(DomainException.class)
                .satisfies(e -> {
                    DomainException de = (DomainException) e;
                    assertThat(de.getStatus()).isEqualTo(BAD_REQUEST);
                    assertThat(de.getCode()).isEqualTo(ErrorCode.OCR_DOCUMENT_NOT_UPLOADED);
                });
    }

    @Test
    void startOcr_throwsServiceUnavailable_whenOcrServiceIsDown() {
        UUID docId = UUID.randomUUID();
        Document doc = Document.builder().id(docId).status(DocumentStatus.UPLOADED)
                .filePath("test.pdf").build();
        when(documentService.getDocumentById(docId)).thenReturn(doc);
        when(ocrHealthClient.isHealthy()).thenReturn(false);

        assertThatThrownBy(() -> ocrService.startOcr(docId, null, UUID.randomUUID()))
                .isInstanceOf(DomainException.class)
                .satisfies(e -> assertThat(((DomainException) e).getCode())
                        .isEqualTo(ErrorCode.OCR_SERVICE_UNAVAILABLE));
    }

    @Test
    void startOcr_createsJobAndDispatchesAsync() {
        UUID docId = UUID.randomUUID();
        UUID userId = UUID.randomUUID();
        UUID jobId = UUID.randomUUID();
        Document doc = Document.builder().id(docId).status(DocumentStatus.UPLOADED)
                .filePath("test.pdf").scriptType(ScriptType.TYPEWRITER).build();
        when(documentService.getDocumentById(docId)).thenReturn(doc);
        when(ocrHealthClient.isHealthy()).thenReturn(true);
        when(ocrJobRepository.save(any())).thenAnswer(inv -> {
            OcrJob job = inv.getArgument(0);
            job.setId(jobId);
            return job;
        });

        UUID result = ocrService.startOcr(docId, null, userId);

        assertThat(result).isEqualTo(jobId);
        verify(ocrAsyncRunner).runSingleDocument(jobId, docId, userId, false);
    }

    @Test
    void startOcr_updatesScriptType_whenProvided() {
        UUID docId = UUID.randomUUID();
        Document doc = Document.builder().id(docId).status(DocumentStatus.UPLOADED)
                .filePath("test.pdf").scriptType(ScriptType.UNKNOWN).build();
        when(documentService.getDocumentById(docId)).thenReturn(doc);
        when(ocrHealthClient.isHealthy()).thenReturn(true);
        when(ocrJobRepository.save(any())).thenAnswer(inv -> {
            OcrJob job = inv.getArgument(0);
            job.setId(UUID.randomUUID());
            return job;
        });

        ocrService.startOcr(docId, ScriptType.HANDWRITING_LATIN, UUID.randomUUID());

        verify(documentService).updateScriptType(docId, ScriptType.HANDWRITING_LATIN);
    }
}
