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

import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;

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

import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameFailureFileNotFound;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameFailureTargetExists;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenamePort;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameResult;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameTechnicalFailure;

/**
 * Filesystem-basierte Implementierung von {@link TargetFileRenamePort}.
 * <p>
 * Benennt eine bestehende Datei im konfigurierten Zielordner um, indem sie
 * {@link Files#move} mit {@link StandardCopyOption#ATOMIC_MOVE} verwendet. Wird
 * {@link AtomicMoveNotSupportedException} geworfen (z. B. auf Netzlaufwerken), erfolgt
 * ein automatischer Rückfall auf einen nicht-atomaren {@code Files.move}-Aufruf.
 * <p>
 * <strong>Architekturgrenze:</strong> Alle NIO-Operationen ({@code Path}, {@code Files})
 * sind ausschließlich in dieser Klasse gekapselt. Der Port
 * {@link TargetFileRenamePort} enthält keine Dateisystem-Typen.
 */
public class FilesystemTargetFileRenameAdapter implements TargetFileRenamePort {

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

    private final Path targetFolder;

    /**
     * Erstellt den Adapter für den angegebenen Zielordner.
     *
     * @param targetFolder Pfad des Zielordners; darf nicht null sein
     * @throws NullPointerException wenn {@code targetFolder} null ist
     */
    public FilesystemTargetFileRenameAdapter(Path targetFolder) {
        this.targetFolder = Objects.requireNonNull(targetFolder, "targetFolder darf nicht null sein");
    }

    /**
     * Benennt eine bestehende Datei im Zielordner von {@code oldFileName} zu
     * {@code newFileName} um.
     * <p>
     * Ablauf:
     * <ol>
     *   <li>Prüft, ob {@code oldFileName} im Zielordner vorhanden ist; falls nicht,
     *       wird {@link TargetFileRenameFailureFileNotFound} zurückgegeben.</li>
     *   <li>Prüft, ob {@code newFileName} bereits durch eine andere Datei belegt ist;
     *       falls ja, wird {@link TargetFileRenameFailureTargetExists} zurückgegeben.</li>
     *   <li>Versucht {@link StandardCopyOption#ATOMIC_MOVE}; bei
     *       {@link AtomicMoveNotSupportedException} (z. B. Netzlaufwerk) erfolgt ein
     *       Rückfall auf einen normalen Verschiebeaufruf ohne Atomic-Flag.</li>
     *   <li>Bei Erfolg: {@link TargetFileRenameSuccess}.</li>
     *   <li>Bei anderen {@link IOException}: {@link TargetFileRenameTechnicalFailure}
     *       mit deutschem Fehlertext.</li>
     * </ol>
     *
     * @param oldFileName der aktuell im Zielordner vorhandene Dateiname (ohne Pfad);
     *                    darf nicht null sein
     * @param newFileName der gewünschte neue Dateiname (ohne Pfad); darf nicht null sein
     * @return das Ergebnis der Umbenennung; nie null
     */
    @Override
    public TargetFileRenameResult rename(String oldFileName, String newFileName) {
        Objects.requireNonNull(oldFileName, "oldFileName darf nicht null sein");
        Objects.requireNonNull(newFileName, "newFileName darf nicht null sein");

        Path oldPath = targetFolder.resolve(oldFileName);
        Path newPath = targetFolder.resolve(newFileName);

        if (Files.notExists(oldPath)) {
            LOG.warn("Umbenennung verweigert: Quelldatei nicht vorhanden: '{}'", oldPath);
            return new TargetFileRenameFailureFileNotFound(oldFileName);
        }

        if (Files.exists(newPath) && !oldPath.equals(newPath)) {
            LOG.warn("Umbenennung verweigert: Zieldatei bereits vorhanden: '{}'", newPath);
            return new TargetFileRenameFailureTargetExists(newFileName);
        }

        try {
            try {
                Files.move(oldPath, newPath, StandardCopyOption.ATOMIC_MOVE);
            } catch (AtomicMoveNotSupportedException atomicEx) {
                LOG.warn("Atomares Verschieben nicht unterstützt (z. B. Netzlaufwerk) für '{}' → '{}'. " +
                        "Rückfall auf normales Verschieben.", oldPath, newPath);
                Files.move(oldPath, newPath);
            }
            LOG.info("Datei erfolgreich umbenannt: '{}' → '{}'", oldFileName, newFileName);
            return new TargetFileRenameSuccess();
        } catch (IOException e) {
            String message = "Technischer Fehler beim Umbenennen von '" + oldFileName
                    + "' zu '" + newFileName + "': " + e.getMessage();
            LOG.error(message, e);
            return new TargetFileRenameTechnicalFailure(message);
        }
    }
}
