using ClawdDotNet.Core.Storage;

namespace ClawdDotNet.Tools.FileRW;

/// <summary>
/// Löst relative Pfade innerhalb eines Workspace auf und stellt sicher, dass sie
/// diesen nicht verlassen.
///
/// Hintergrund (S6): Die Prüfung verglich vorher nur die Zeichenkette:
///
///     fullPath.StartsWith(rootPath, OrdinalIgnoreCase)
///
/// Ohne abschließenden Verzeichnistrenner erlaubte ein Root wie
/// "…\Agent-X\Workspace" damit auch Zugriffe auf "…\Agent-X\Workspace-Backup\…" —
/// ein fremdes Verzeichnis, das zufällig mit demselben Präfix beginnt.
///
/// Der Vergleich selbst liegt in <see cref="PathBoundary"/>: Unter Linux
/// unterscheidet das Dateisystem Groß- und Kleinschreibung, und symbolische
/// Verknüpfungen können aus dem Workspace hinauszeigen, ohne dass die reine
/// Pfadrechnung das sieht.
/// </summary>
public static class WorkspacePath
{
    /// <summary>
    /// Setzt <paramref name="relativePath"/> auf <paramref name="root"/> auf und prüft,
    /// dass das Ergebnis innerhalb des Workspace liegt.
    /// </summary>
    /// <exception cref="UnauthorizedAccessException">Wenn der Pfad ausbricht.</exception>
    public static string Resolve(string root, string? relativePath, string workspaceName)
    {
        var value = string.IsNullOrWhiteSpace(relativePath) ? "." : relativePath.Trim();

        // Absolute Pfade und UNC-Freigaben würden Path.Combine den Root verwerfen lassen.
        if (Path.IsPathRooted(value) || value.StartsWith(@"\\") || value.StartsWith("//"))
        {
            throw new UnauthorizedAccessException(
                $"Zugriff verweigert: Absolute Pfade sind im {workspaceName} Workspace nicht erlaubt.");
        }

        // Alternate Data Streams (datei.txt:versteckt) umgehen die Endungsprüfung.
        //
        // Die Prüfung trägt unter Linux zusätzlich: Dort meldet Path.IsPathRooted für
        // "C:\Windows\…" false — ein Windows-Pfad ist unter Linux ein gewöhnlicher
        // Dateiname. Ohne diese Zeile käme er als relativer Pfad durch. Ein Doppelpunkt
        // im Dateinamen ist unter Linux zwar erlaubt, aber nichts, was ein Agent
        // braucht; die Einschränkung kostet uns nichts.
        if (value.Contains(':'))
        {
            throw new UnauthorizedAccessException(
                $"Zugriff verweigert: Der Pfad enthält ein unzulässiges Zeichen (':').");
        }

        var normalizedRoot = NormalizeDirectory(root);
        var fullPath = Path.GetFullPath(Path.Combine(normalizedRoot, value));

        if (!IsInside(fullPath, normalizedRoot))
        {
            throw new UnauthorizedAccessException(
                $"Zugriff verweigert: Der Pfad liegt außerhalb des {workspaceName} Workspaces.");
        }

        return fullPath;
    }

    /// <summary>
    /// Prüft, ob <paramref name="candidate"/> im Verzeichnis <paramref name="root"/> liegt.
    /// Der Vergleich erfolgt auf Verzeichnisgrenzen, nicht auf Zeichenketten-Präfixen,
    /// und löst symbolische Verknüpfungen auf.
    /// </summary>
    public static bool IsInside(string candidate, string root)
        => PathBoundary.IsInside(candidate, root);

    /// <summary>Absoluter Pfad mit genau einem abschließenden Trenner.</summary>
    private static string NormalizeDirectory(string path)
        => PathBoundary.NormalizeDirectory(path);
}
