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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

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

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.PdfExtractionSuccess;
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;

/**
 * Tests for {@link PreCheckEvaluator}.
 * <p>
 * Verifies correct pre-check logic for usable text and page limit validation.
 */
class PreCheckEvaluatorTest {

    @TempDir
    Path tempDir;

    @Test
    void evaluate_passesWhenDocumentHasUsableTextAndValidPageCount() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Some meaningful text", new PdfPageCount(5));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass when text is usable and page count is valid");
        PreCheckPassed passed = (PreCheckPassed) result;
        assertSame(passed.candidate(), candidate, "Candidate should be preserved");
        assertSame(passed.extraction(), extraction, "Extraction should be preserved");
    }

    @Test
    void evaluate_failsWithNoUsableTextWhenExtractedTextIsEmpty() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckFailed, "Should fail with empty text");
        PreCheckFailed failed = (PreCheckFailed) result;
        assertEquals(PreCheckFailureReason.NO_USABLE_TEXT, failed.failureReason());
    }

    @Test
    void evaluate_failsWithNoUsableTextWhenTextIsOnlyWhitespace() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("   \n\t  \r\n  ", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckFailed, "Should fail with whitespace-only text");
        PreCheckFailed failed = (PreCheckFailed) result;
        assertEquals(PreCheckFailureReason.NO_USABLE_TEXT, failed.failureReason());
    }

    @Test
    void evaluate_failsWithNoUsableTextWhenTextContainsOnlySpecialCharacters() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("!@#$%^&*()_+-=[]{}|;:',.<>?/", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckFailed, "Should fail with special characters only");
        PreCheckFailed failed = (PreCheckFailed) result;
        assertEquals(PreCheckFailureReason.NO_USABLE_TEXT, failed.failureReason());
    }

    @Test
    void evaluate_passesWithTextContainingSingleLetter() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("a", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass with single letter");
    }

    @Test
    void evaluate_passesWithTextContainingSingleDigit() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("5", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass with single digit");
    }

    @Test
    void evaluate_passesWithTextMixedWithSpecialCharactersIfLettersOrDigitsPresent() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("!@#a$%^&*", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass when letters/digits are present among special chars");
    }

    @Test
    void evaluate_passesWithWhitespaceAroundUsableText() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("   meaningful text   ", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass when text has meaningful content despite whitespace");
    }

    @Test
    void evaluate_failsWithPageLimitExceededWhenPageCountEqualsLimit() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(5));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(5));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass when page count equals limit (not exceeded)");
    }

    @Test
    void evaluate_failsWithPageLimitExceededWhenPageCountExceedsLimit() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(5));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(6));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckFailed, "Should fail when page count exceeds limit");
        PreCheckFailed failed = (PreCheckFailed) result;
        assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED, failed.failureReason());
    }

    @Test
    void evaluate_failsWithPageLimitExceededEvenIfTextIsValid() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(2));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Excellent meaningful text with lots of content", new PdfPageCount(100));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckFailed, "Should fail with page limit exceeded even if text is good");
        PreCheckFailed failed = (PreCheckFailed) result;
        assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED, failed.failureReason());
    }

    @Test
    void evaluate_prefersPageLimitCheckOverTextCheck() throws Exception {
        // If both checks fail, page limit check should take precedence (not tested for priority,
        // but we verify that one failure is reported consistently)
        RuntimeConfiguration config = buildConfig(maxPages(1));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("", new PdfPageCount(10));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckFailed, "Should fail when both checks fail");
        // The specific order of checks doesn't matter; just verify one reason is returned
        PreCheckFailed failed = (PreCheckFailed) result;
        assertNotNull(failed.failureReason());
    }

    @Test
    void evaluate_throwsNullPointerExceptionWhenCandidateIsNull() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));

        assertThrows(NullPointerException.class, () ->
            PreCheckEvaluator.evaluate(null, extraction, config)
        );
    }

    @Test
    void evaluate_throwsNullPointerExceptionWhenExtractionIsNull() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();

        assertThrows(NullPointerException.class, () ->
            PreCheckEvaluator.evaluate(candidate, null, config)
        );
    }

    @Test
    void evaluate_throwsNullPointerExceptionWhenConfigurationIsNull() throws Exception {
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));

        assertThrows(NullPointerException.class, () ->
            PreCheckEvaluator.evaluate(candidate, extraction, null)
        );
    }

    @Test
    void evaluate_passesWithUnicodeGermanUmlauts() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Äußerst äöüß Großes", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass with German umlauts (ÄÖÜß)");
    }

    @Test
    void evaluate_passesWithOtherUnicodeCharacters() throws Exception {
        RuntimeConfiguration config = buildConfig(maxPages(10));
        SourceDocumentCandidate candidate = buildCandidate();
        PdfExtractionSuccess extraction = new PdfExtractionSuccess("Αβγδ 中文 καλημέρα", new PdfPageCount(1));

        DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);

        assertTrue(result instanceof PreCheckPassed, "Should pass with Greek, Chinese, and other Unicode letters");
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    private RuntimeConfiguration buildConfig(int maxPages) throws Exception {
        return new RuntimeConfiguration(maxPages, 3, AiContentSensitivity.PROTECT_SENSITIVE_CONTENT);
    }

    private int maxPages(int limit) {
        return limit;
    }

    private SourceDocumentCandidate buildCandidate() throws Exception {
        Path sourceDir = Files.createDirectories(tempDir.resolve("source"));
        Path pdfFile = sourceDir.resolve("test.pdf");
        Files.createFile(pdfFile);
        SourceDocumentLocator locator = new SourceDocumentLocator(pdfFile.toString());
        return new SourceDocumentCandidate(pdfFile.getFileName().toString(), 0L, locator);
    }
}