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

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
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.port.out.RunLockUnavailableException;

/**
 * Unit tests for {@link FilesystemRunLockPortAdapter}.
 * <p>
 * Tests cover acquire/release success path, lock contention behavior,
 * and robust release behavior.
 */
class FilesystemRunLockPortAdapterTest {

    @TempDir
    Path tempDir;

    @Test
    void acquire_createsLockFile() {
        Path lockFile = tempDir.resolve("test.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        adapter.acquire();

        assertTrue(Files.exists(lockFile), "Lock file should exist after acquire");
    }

    @Test
    void acquire_writesProcessPidToLockFile() throws Exception {
        Path lockFile = tempDir.resolve("test.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        adapter.acquire();

        String content = Files.readString(lockFile);
        long pid = ProcessHandle.current().pid();
        assertEquals(String.valueOf(pid), content, "Lock file should contain current PID");
    }

    @Test
    void release_deletesLockFile() throws Exception {
        Path lockFile = tempDir.resolve("test.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);
        adapter.acquire();
        assertTrue(Files.exists(lockFile));

        adapter.release();

        assertFalse(Files.exists(lockFile), "Lock file should be deleted after release");
    }

    @Test
    void release_doesNotThrowIfLockFileAbsent() {
        Path lockFile = tempDir.resolve("nonexistent.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        assertDoesNotThrow(adapter::release, "Release without prior acquire should not throw");
    }

    @Test
    void acquire_throwsRunLockUnavailableExceptionIfLockFileExists() throws Exception {
        Path lockFile = tempDir.resolve("test.lock");
        Files.writeString(lockFile, "12345");

        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        assertThrows(RunLockUnavailableException.class, adapter::acquire,
                "Acquire should throw RunLockUnavailableException when lock file already exists");
    }

    @Test
    void acquire_exceptionMessageContainsLockFilePath() throws Exception {
        Path lockFile = tempDir.resolve("test.lock");
        Files.writeString(lockFile, "12345");

        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        RunLockUnavailableException ex = assertThrows(RunLockUnavailableException.class, adapter::acquire);
        assertTrue(ex.getMessage().contains(lockFile.toString()),
                "Exception message should contain lock file path");
    }

    @Test
    void acquire_createsParentDirectoriesIfAbsent() {
        Path lockFile = tempDir.resolve("nested").resolve("deep").resolve("test.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        adapter.acquire();

        assertTrue(Files.exists(lockFile), "Lock file should be created including parent dirs");
    }

    @Test
    void acquireAndRelease_canBeRepeated() {
        Path lockFile = tempDir.resolve("test.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        adapter.acquire();
        adapter.release();
        adapter.acquire();
        adapter.release();

        assertFalse(Files.exists(lockFile), "Lock file should not exist after second release");
    }

    @Test
    void acquire_secondCallThrowsRunLockUnavailableException() {
        Path lockFile = tempDir.resolve("test.lock");
        FilesystemRunLockPortAdapter adapter = new FilesystemRunLockPortAdapter(lockFile);

        adapter.acquire();

        assertThrows(RunLockUnavailableException.class, adapter::acquire,
                "Second acquire without release should throw RunLockUnavailableException");
    }
}
