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

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;

/**
 * File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs.
 * <p>
 * Creates an exclusive lock file on acquire and deletes it on release.
 * If the lock file already exists, {@link #acquire()} throws {@link RunLockUnavailableException}
 * to signal that another instance is already running.
 * <p>
 * The lock file contains the PID of the acquiring process. Release is best-effort: a failure
 * to delete the lock file is logged as a warning but does not throw.
 */
public class FilesystemRunLockPortAdapter implements RunLockPort {

    private static final Logger LOG = LogManager.getLogger(FilesystemRunLockPortAdapter.class);

    private final Path lockFile;

    /**
     * Creates a new FilesystemRunLockPortAdapter for the given lock file path.
     *
     * @param lockFile path of the lock file to create on acquire and delete on release
     */
    public FilesystemRunLockPortAdapter(Path lockFile) {
        this.lockFile = lockFile;
    }

    /**
     * Acquires the run lock by creating the lock file.
     * <p>
     * If the lock file already exists, throws {@link RunLockUnavailableException}.
     * If the parent directory does not exist, it is created before attempting file creation.
     *
     * @throws RunLockUnavailableException if the lock file already exists or cannot be created
     */
    @Override
    public void acquire() {
        if (Files.exists(lockFile)) {
            throw new RunLockUnavailableException(
                    "Run lock file already exists - another instance may be running: " + lockFile);
        }
        try {
            Path parent = lockFile.getParent();
            if (parent != null) {
                Files.createDirectories(parent);
            }
            long pid = ProcessHandle.current().pid();
            Files.writeString(lockFile, String.valueOf(pid), StandardOpenOption.CREATE_NEW);
            LOG.debug("Run lock acquired: {} (PID {})", lockFile, pid);
        } catch (IOException e) {
            throw new RunLockUnavailableException("Failed to acquire run lock file: " + lockFile, e);
        }
    }

    /**
     * Releases the run lock by deleting the lock file.
     * <p>
     * If deletion fails, a warning is logged but no exception is thrown.
     */
    @Override
    public void release() {
        try {
            boolean deleted = Files.deleteIfExists(lockFile);
            if (deleted) {
                LOG.debug("Run lock released: {}", lockFile);
            }
        } catch (IOException e) {
            LOG.warn("Failed to release run lock file: {} — manual cleanup may be required", lockFile, e);
        }
    }
}
