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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.nio.file.Files;
import java.nio.file.Path;

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.config.RuntimeConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;

/**
 * Tests for {@link DocumentProcessingService}.
 * <p>
 * Verifies that all document processing outcomes are correctly classified.
 */
class DocumentProcessingServiceTest {

    @TempDir
    Path tempDir;

    private SourceDocumentCandidate candidate;
    private RuntimeConfiguration runtimeConfig;

    @BeforeEach
    void setUp() throws Exception {
        // Create test PDF file
        Path pdfFile = tempDir.resolve("document.pdf");
        Files.createFile(pdfFile);
        SourceDocumentLocator locator = new SourceDocumentLocator(pdfFile.toString());
        candidate = new SourceDocumentCandidate("document.pdf", 2048L, locator);

        // Create runtime configuration with maxPages limit and default transient retry limit
        runtimeConfig = new RuntimeConfiguration(10, 3, AiContentSensitivity.PROTECT_SENSITIVE_CONTENT);
    }

    @Test
    void testProcessDocument_WithSuccessfulExtraction_AndPassedPreChecks() {
        // Arrange: Successful extraction with valid text
        var extraction = new PdfExtractionSuccess("This is valid PDF text", new PdfPageCount(5));

        // Act
        DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
                candidate, extraction, runtimeConfig);

        // Assert: Should produce PreCheckPassed
        assertInstanceOf(PreCheckPassed.class, outcome);
        PreCheckPassed passed = (PreCheckPassed) outcome;
        assertEquals(candidate, passed.candidate());
        assertEquals(extraction, passed.extraction());
    }

    @Test
    void testProcessDocument_WithSuccessfulExtraction_AndFailedPreCheck_NoUsableText() {
        // Arrange: Successful extraction but with only whitespace
        var extraction = new PdfExtractionSuccess("   \n   \t   ", new PdfPageCount(1));

        // Act
        DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
                candidate, extraction, runtimeConfig);

        // Assert: Should produce PreCheckFailed with appropriate reason
        assertInstanceOf(PreCheckFailed.class, outcome);
        PreCheckFailed failed = (PreCheckFailed) outcome;
        assertEquals(candidate, failed.candidate());
        assertEquals(PreCheckFailureReason.NO_USABLE_TEXT, failed.failureReason());
    }

    @Test
    void testProcessDocument_WithSuccessfulExtraction_AndFailedPreCheck_PageLimitExceeded() {
        // Arrange: Successful extraction but exceeds page limit
        var extraction = new PdfExtractionSuccess("Valid text content", new PdfPageCount(50));

        // Act
        DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
                candidate, extraction, runtimeConfig);

        // Assert: Should produce PreCheckFailed with page limit reason
        assertInstanceOf(PreCheckFailed.class, outcome);
        PreCheckFailed failed = (PreCheckFailed) outcome;
        assertEquals(candidate, failed.candidate());
        assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED, failed.failureReason());
    }

    @Test
    void testProcessDocument_WithContentError() {
        // Arrange: PDF content not extractable (classified as pre-check failed)
        var contentError = new PdfExtractionContentError("PDF is corrupted");

        // Act
        DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
                candidate, contentError, runtimeConfig);

        // Assert: Should produce PreCheckFailed
        assertInstanceOf(PreCheckFailed.class, outcome);
        PreCheckFailed result = (PreCheckFailed) outcome;
        assertEquals(candidate, result.candidate());
        assertEquals(PreCheckFailureReason.CONTENT_NOT_EXTRACTABLE, result.failureReason());
    }

    @Test
    void testProcessDocument_WithTechnicalError() {
        // Arrange: Technical error during extraction
        var technicalError = new PdfExtractionTechnicalError("I/O error reading file",
                new RuntimeException("File not found"));

        // Act
        DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
                candidate, technicalError, runtimeConfig);

        // Assert: Should produce TechnicalDocumentError
        assertInstanceOf(TechnicalDocumentError.class, outcome);
        TechnicalDocumentError result = (TechnicalDocumentError) outcome;
        assertEquals(candidate, result.candidate());
        assertEquals("I/O error reading file", result.errorMessage());
        assertNotNull(result.cause());
    }

    @Test
    void testProcessDocument_WithTechnicalError_WithoutCause() {
        // Arrange: Technical error without underlying exception
        var technicalError = new PdfExtractionTechnicalError("Unknown error", null);

        // Act
        DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
                candidate, technicalError, runtimeConfig);

        // Assert
        assertInstanceOf(TechnicalDocumentError.class, outcome);
        TechnicalDocumentError result = (TechnicalDocumentError) outcome;
        assertNull(result.cause());
    }

    @Test
    void testProcessDocument_WithNullCandidate_ThrowsException() {
        // Arrange
        var extraction = new PdfExtractionSuccess("Text", new PdfPageCount(1));

        // Act & Assert
        assertThrows(NullPointerException.class,
                () -> DocumentProcessingService.processDocument(null, extraction, runtimeConfig));
    }

    @Test
    void testProcessDocument_WithNullExtractionResult_ThrowsException() {
        // Act & Assert
        assertThrows(NullPointerException.class,
                () -> DocumentProcessingService.processDocument(candidate, null, runtimeConfig));
    }

    @Test
    void testProcessDocument_WithNullConfiguration_ThrowsException() {
        // Arrange
        var extraction = new PdfExtractionSuccess("Text", new PdfPageCount(1));

        // Act & Assert
        assertThrows(NullPointerException.class,
                () -> DocumentProcessingService.processDocument(candidate, extraction, null));
    }
}