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

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

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

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.SourceDocumentAccessException;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;

/**
 * Tests for {@link SourceDocumentCandidatesPortAdapter}.
 */
class SourceDocumentCandidatesPortAdapterTest {

    @TempDir
    Path tempDir;

    private SourceDocumentCandidatesPortAdapter adapter;

    @BeforeEach
    void setUp() {
        adapter = new SourceDocumentCandidatesPortAdapter(tempDir);
    }

    @Test
    void testLoadCandidates_EmptyFolder() throws IOException {
        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertNotNull(candidates);
        assertTrue(candidates.isEmpty(), "Empty folder should return empty list");
    }

    @Test
    void testLoadCandidates_OnlyPdfFiles() throws IOException {
        // Create test PDF files
        Path pdf1 = tempDir.resolve("document1.pdf");
        Path pdf2 = tempDir.resolve("document2.pdf");
        Files.write(pdf1, "pdf content".getBytes());
        Files.write(pdf2, "pdf content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(2, candidates.size(), "Should return exactly 2 PDF candidates");
        assertTrue(candidates.stream()
                .allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
            "All candidates should be PDF files");
    }

    @Test
    void testLoadCandidates_FiltersNonPdfFiles() throws IOException {
        // Create mixed file types
        Files.write(tempDir.resolve("document.pdf"), "content".getBytes());
        Files.write(tempDir.resolve("image.png"), "content".getBytes());
        Files.write(tempDir.resolve("text.txt"), "content".getBytes());
        Files.write(tempDir.resolve("data.xlsx"), "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(1, candidates.size(), "Should return only 1 PDF candidate");
        assertEquals("document.pdf", candidates.get(0).uniqueIdentifier());
    }

    @Test
    void testLoadCandidates_IgnoresDirectories() throws IOException {
        // Create files and subdirectories
        Files.write(tempDir.resolve("document.pdf"), "content".getBytes());
        Files.createDirectory(tempDir.resolve("subfolder"));
        Files.write(tempDir.resolve("subfolder/nested.pdf"), "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(1, candidates.size(), "Should return only 1 PDF candidate (in root folder)");
        assertEquals("document.pdf", candidates.get(0).uniqueIdentifier());
    }

    @Test
    void testLoadCandidates_CaseInsensitiveExtension() throws IOException {
        // Create PDFs with various case combinations
        Files.write(tempDir.resolve("file1.pdf"), "content".getBytes());
        Files.write(tempDir.resolve("file2.PDF"), "content".getBytes());
        Files.write(tempDir.resolve("file3.Pdf"), "content".getBytes());
        Files.write(tempDir.resolve("file4.pDf"), "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(4, candidates.size(), "Should recognize PDF in any case combination");
    }

    @Test
    void testLoadCandidates_DeterministicOrder() throws IOException {
        // Create PDFs in non-alphabetical order
        Files.write(tempDir.resolve("zebra.pdf"), "content".getBytes());
        Files.write(tempDir.resolve("apple.pdf"), "content".getBytes());
        Files.write(tempDir.resolve("monkey.pdf"), "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(3, candidates.size());
        // Files are sorted by absolute path, which will be consistent
        List<SourceDocumentCandidate> candidates2 = adapter.loadCandidates();
        assertEquals(candidates, candidates2, "Multiple calls should return same order");
    }

    @Test
    void testLoadCandidates_FileSizeMetadata() throws IOException {
        Path pdfFile = tempDir.resolve("test.pdf");
        Files.write(pdfFile, "test content 12345".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(1, candidates.size());
        SourceDocumentCandidate candidate = candidates.get(0);
        assertEquals(18, candidate.fileSizeBytes(), "File size should match written content");
    }

    @Test
    void testLoadCandidates_UniqueIdentifier() throws IOException {
        Path pdfFile = tempDir.resolve("myfile.pdf");
        Files.write(pdfFile, "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(1, candidates.size());
        assertEquals("myfile.pdf", candidates.get(0).uniqueIdentifier(),
            "uniqueIdentifier should be filename");
    }

    @Test
    void testLoadCandidates_LocatorContainsAbsolutePath() throws IOException {
        Path pdfFile = tempDir.resolve("test.pdf");
        Files.write(pdfFile, "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(1, candidates.size());
        String locatorValue = candidates.get(0).locator().value();
        assertTrue(locatorValue.contains("test.pdf"), "Locator should contain filename");
        assertTrue(new java.io.File(locatorValue).isAbsolute(),
            "Locator value should be an absolute path");
    }

    @Test
    void testLoadCandidates_SourceFolderNotFound() {
        Path nonExistentFolder = tempDir.resolve("does-not-exist");
        SourceDocumentCandidatesPortAdapter adapterForMissing =
            new SourceDocumentCandidatesPortAdapter(nonExistentFolder);

        SourceDocumentAccessException ex = assertThrows(
            SourceDocumentAccessException.class,
            adapterForMissing::loadCandidates,
            "Should throw exception for non-existent source folder");

        assertTrue(ex.getMessage().contains("does not exist"));
    }

    @Test
    void testLoadCandidates_SourceFolderIsFile() throws IOException {
        Path fileInsteadOfFolder = tempDir.resolve("regular-file");
        Files.createFile(fileInsteadOfFolder);

        SourceDocumentCandidatesPortAdapter adapterForFile =
            new SourceDocumentCandidatesPortAdapter(fileInsteadOfFolder);

        SourceDocumentAccessException ex = assertThrows(
            SourceDocumentAccessException.class,
            adapterForFile::loadCandidates,
            "Should throw exception if source path is a file, not a folder");

        assertTrue(ex.getMessage().contains("not a directory"));
    }

    @Test
    void testLoadCandidates_HasLocatorForEachCandidate() throws IOException {
        Files.createFile(tempDir.resolve("file1.pdf"));
        Files.createFile(tempDir.resolve("file2.pdf"));

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        for (SourceDocumentCandidate candidate : candidates) {
            assertNotNull(candidate.locator(), "Each candidate must have a locator");
            assertNotNull(candidate.locator().value(), "Locator value must not be null");
            assertFalse(candidate.locator().value().isEmpty(), "Locator value must not be empty");
        }
    }

    @Test
    void testLoadCandidates_EmptyPdfFilesAreIncluded() throws IOException {
        // Create empty PDF files
        Files.createFile(tempDir.resolve("empty1.pdf"));
        Files.createFile(tempDir.resolve("empty2.pdf"));
        // Also add a non-empty PDF for contrast
        Files.write(tempDir.resolve("nonempty.pdf"), "content".getBytes());

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(3, candidates.size(),
            "Empty PDF files should be included as candidates; content evaluation happens during document processing");
        assertTrue(candidates.stream().allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
            "All candidates should be PDF files");
    }

    /**
     * A directory whose name ends with {@code .pdf} must not be included as a candidate.
     * <p>
     * The regular-file filter must exclude directories even when their name matches the
     * PDF extension, so that only actual PDF files are returned.
     */
    @Test
    void testLoadCandidates_DirectoryWithPdfExtensionIsExcluded() throws IOException {
        Files.write(tempDir.resolve("real.pdf"), "content".getBytes());
        Files.createDirectory(tempDir.resolve("looks-like.pdf"));

        List<SourceDocumentCandidate> candidates = adapter.loadCandidates();

        assertEquals(1, candidates.size(),
                "A directory with .pdf extension must not be included as a candidate");
        assertEquals("real.pdf", candidates.get(0).uniqueIdentifier());
    }
}
