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

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.IOException;
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.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;

/**
 * Unit-Tests für {@link FilesystemPathCheckAdapter}.
 * <p>
 * Prüft alle vier Methoden des Ports unter realen Dateisystem-Bedingungen mit
 * {@link TempDir}. Windows-spezifische Tests werden auf Nicht-Windows-Systemen
 * automatisch übersprungen.
 */
class FilesystemPathCheckAdapterTest {

    @TempDir
    Path tempDir;

    private FilesystemPathCheckAdapter adapter;

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

    // -----------------------------------------------------------------------
    // isDirectoryReadable
    // -----------------------------------------------------------------------

    @Test
    void isDirectoryReadable_existingReadableDirectory_returnsTrue() {
        assertTrue(adapter.isDirectoryReadable(tempDir.toString()));
    }

    @Test
    void isDirectoryReadable_nonExistentPath_returnsFalse() {
        Path absent = tempDir.resolve("does-not-exist");
        assertFalse(adapter.isDirectoryReadable(absent.toString()));
    }

    @Test
    void isDirectoryReadable_existingFile_returnsFalse() throws IOException {
        Path file = Files.createFile(tempDir.resolve("some-file.txt"));
        assertFalse(adapter.isDirectoryReadable(file.toString()));
    }

    @Test
    void isDirectoryReadable_emptyString_returnsFalse() {
        assertFalse(adapter.isDirectoryReadable(""));
    }

    @Test
    void isDirectoryReadable_nullValue_returnsFalse() {
        assertFalse(adapter.isDirectoryReadable(null));
    }

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void isDirectoryReadable_invalidWindowsCharacters_returnsFalse() {
        // Zeichen wie '<', '>', '?' sind auf Windows in Pfaden unzulässig
        assertFalse(adapter.isDirectoryReadable("C:\\invalid<path>?"));
    }

    // -----------------------------------------------------------------------
    // isDirectoryWritableOrCreatable
    // -----------------------------------------------------------------------

    @Test
    void isDirectoryWritableOrCreatable_existingWritableDirectory_returnsTrue() {
        assertTrue(adapter.isDirectoryWritableOrCreatable(tempDir.toString()));
    }

    @Test
    void isDirectoryWritableOrCreatable_nonExistentDirectoryWithWritableParent_returnsTrue() {
        Path newDir = tempDir.resolve("new-sub-dir");
        assertTrue(adapter.isDirectoryWritableOrCreatable(newDir.toString()));
    }

    @Test
    void isDirectoryWritableOrCreatable_nonExistentDirectoryAndNonExistentParent_returnsFalse() {
        Path deepAbsent = tempDir.resolve("ghost").resolve("deeply").resolve("nested");
        assertFalse(adapter.isDirectoryWritableOrCreatable(deepAbsent.toString()));
    }

    @Test
    void isDirectoryWritableOrCreatable_emptyString_returnsFalse() {
        assertFalse(adapter.isDirectoryWritableOrCreatable(""));
    }

    @Test
    void isDirectoryWritableOrCreatable_nullValue_returnsFalse() {
        assertFalse(adapter.isDirectoryWritableOrCreatable(null));
    }

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void isDirectoryWritableOrCreatable_invalidWindowsCharacters_returnsFalse() {
        assertFalse(adapter.isDirectoryWritableOrCreatable("C:\\invalid<path>?"));
    }

    // -----------------------------------------------------------------------
    // isFileReadable
    // -----------------------------------------------------------------------

    @Test
    void isFileReadable_existingReadableFile_returnsTrue() throws IOException {
        Path file = Files.createFile(tempDir.resolve("readable.txt"));
        assertTrue(adapter.isFileReadable(file.toString()));
    }

    @Test
    void isFileReadable_nonExistentFile_returnsFalse() {
        Path absent = tempDir.resolve("missing.txt");
        assertFalse(adapter.isFileReadable(absent.toString()));
    }

    @Test
    void isFileReadable_existingDirectory_returnsFalse() {
        assertFalse(adapter.isFileReadable(tempDir.toString()));
    }

    @Test
    void isFileReadable_emptyString_returnsFalse() {
        assertFalse(adapter.isFileReadable(""));
    }

    @Test
    void isFileReadable_nullValue_returnsFalse() {
        assertFalse(adapter.isFileReadable(null));
    }

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void isFileReadable_invalidWindowsCharacters_returnsFalse() {
        assertFalse(adapter.isFileReadable("C:\\invalid<file>?.txt"));
    }

    // -----------------------------------------------------------------------
    // isSqlitePathUsable
    // -----------------------------------------------------------------------

    @Test
    void isSqlitePathUsable_existingWritableFile_returnsTrue() throws IOException {
        Path db = Files.createFile(tempDir.resolve("test.db"));
        assertTrue(adapter.isSqlitePathUsable(db.toString()));
    }

    @Test
    void isSqlitePathUsable_nonExistentFileWithWritableParentDir_returnsTrue() {
        Path newDb = tempDir.resolve("new.db");
        assertTrue(adapter.isSqlitePathUsable(newDb.toString()));
    }

    @Test
    void isSqlitePathUsable_nonExistentFileAndNonExistentParentDir_returnsFalse() {
        Path deepAbsent = tempDir.resolve("ghost").resolve("sub.db");
        assertFalse(adapter.isSqlitePathUsable(deepAbsent.toString()));
    }

    @Test
    void isSqlitePathUsable_existingDirectory_returnsFalse() {
        // Ein Verzeichnis ist kein gültiger SQLite-Dateipfad
        assertFalse(adapter.isSqlitePathUsable(tempDir.toString()));
    }

    @Test
    void isSqlitePathUsable_emptyString_returnsFalse() {
        assertFalse(adapter.isSqlitePathUsable(""));
    }

    @Test
    void isSqlitePathUsable_nullValue_returnsFalse() {
        assertFalse(adapter.isSqlitePathUsable(null));
    }

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void isSqlitePathUsable_invalidWindowsCharacters_returnsFalse() {
        assertFalse(adapter.isSqlitePathUsable("C:\\invalid<db>?.db"));
    }

    // -----------------------------------------------------------------------
    // Windows-Pfad-Semantik (Syntaxprüfung, kein echtes Laufwerk erforderlich)
    // -----------------------------------------------------------------------

    /**
     * Stellt sicher, dass Pfade mit gemapptem Laufwerksbuchstaben syntaktisch akzeptiert
     * werden (kein sofortiger Syntaxfehler). Für Methoden ohne Elternpfad-Prüfung ist
     * das Ergebnis {@code false}, weil das Laufwerk in dieser Testumgebung nicht existiert.
     * <p>
     * Bei {@link FilesystemPathCheckAdapter#isDirectoryWritableOrCreatable} wird der
     * Elternpfad (das Laufwerk selbst) per Probe-Datei geprüft. Existiert das Laufwerk
     * {@code H:\} auf der Testmaschine und ist schreibbar, ist {@code true} das korrekte
     * Ergebnis — die Prüfung wird in diesem Fall ohne Ergebnis-Assertion ausgeführt.
     */
    @Test
    @EnabledOnOs(OS.WINDOWS)
    void windowsMappedDriveSyntax_isAcceptedByAdapter() {
        assertFalse(adapter.isDirectoryReadable("S:\\nonexistent-in-test"));
        // isDirectoryWritableOrCreatable prüft den Elternpfad per Probe-Datei.
        // Existiert H:\ und ist schreibbar, gibt die Methode korrekt true zurück.
        if (!Files.exists(Path.of("H:\\"))) {
            assertFalse(adapter.isDirectoryWritableOrCreatable("H:\\nonexistent-in-test"));
        } else {
            adapter.isDirectoryWritableOrCreatable("H:\\nonexistent-in-test");
        }
        assertFalse(adapter.isFileReadable("X:\\nonexistent-in-test\\file.txt"));
        assertFalse(adapter.isSqlitePathUsable("Z:\\nonexistent-in-test\\db.db"));
    }

    /**
     * Stellt sicher, dass UNC-Pfade syntaktisch akzeptiert werden.
     * Das Ergebnis ist {@code false}, weil der Server nicht existiert.
     */
    @Test
    @EnabledOnOs(OS.WINDOWS)
    void windowsUncPathSyntax_isAcceptedByAdapter() {
        assertFalse(adapter.isDirectoryReadable("\\\\nonexistent-server\\share\\folder"));
    }

    /**
     * Stellt sicher, dass der Adapter auf dem lokalen temporären Verzeichnis korrekt
     * arbeitet — dieses ist plattformübergreifend immer vorhanden.
     */
    @Test
    void tmpDirIsReadableAndWritableOrCreatable() {
        String tmpDir = System.getProperty("java.io.tmpdir");
        assumeTrue(tmpDir != null && !tmpDir.isBlank(), "java.io.tmpdir must be set");
        assertTrue(adapter.isDirectoryReadable(tmpDir),
                "java.io.tmpdir must be readable: " + tmpDir);
        assertTrue(adapter.isDirectoryWritableOrCreatable(tmpDir),
                "java.io.tmpdir must be writable: " + tmpDir);
    }
}
