namespace ClawdDotNet.Core.Storage;

/// <summary>
/// Wo die Anwendung ihre eigenen Dateien ablegt — getrennt nach Programm und Daten.
///
/// Hintergrund (Linux-Portierung): Bisher lagen Einstellungen und Arbeitsordner neben
/// der Programmdatei (<c>AppDomain.CurrentDomain.BaseDirectory</c>). Unter Windows in
/// einem Benutzerverzeichnis geht das; unter Linux liegt die Anwendung in <c>/opt</c>
/// oder <c>/usr/lib</c> und ist für den Dienstbenutzer <b>nicht schreibbar</b>.
///
/// Deshalb hier die übliche Trennung: Programm bleibt, wo es installiert ist, Daten
/// wandern in das Verzeichnis des Benutzers (XDG unter Linux, <c>%APPDATA%</c> unter
/// Windows). Beides lässt sich per Umgebungsvariable überschreiben — für Dienste, die
/// nach <c>/var/lib</c> schreiben sollen, und für Tests.
/// </summary>
public static class AppPaths
{
    private const string AppFolder = "ClawdDotNet";

    /// <summary>Einstellungen und Schlüssel. Klein, selten geschrieben, gehört gesichert.</summary>
    public static string ConfigDirectory { get; } = ResolveConfig();

    /// <summary>Instanzen, Datenbanken, Logs. Groß, oft geschrieben.</summary>
    public static string DataDirectory { get; } = ResolveData();

    /// <summary>
    /// Das Verzeichnis der Programmdatei.
    ///
    /// Weiterhin nötig, um bestehende Windows-Installationen zu finden: Dort liegt die
    /// <c>AppSettings.json</c> noch am alten Ort, und ein Update darf sie nicht
    /// verwaisen lassen.
    /// </summary>
    public static string ProgramDirectory => AppContext.BaseDirectory;

    /// <summary>Legt das Verzeichnis an und schränkt unter Unix die Rechte auf den Benutzer ein.</summary>
    public static string EnsureDirectory(string path)
    {
        Directory.CreateDirectory(path);

        // 0700: In den Konfigurationsverzeichnissen liegen Schlüssel und Zugangsdaten.
        // Unter Windows regelt das die Vererbung aus dem Benutzerprofil, unter Linux
        // wären es sonst je nach umask 0755 — für alle lesbar.
        if (!OperatingSystem.IsWindows())
        {
            try
            {
                File.SetUnixFileMode(path,
                    UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
            }
            catch (IOException) { /* etwa auf Netzlaufwerken ohne Rechteverwaltung */ }
            catch (UnauthorizedAccessException) { }
        }

        return path;
    }

    /// <summary>Schränkt eine Datei unter Unix auf <c>0600</c> ein. Unter Windows wirkungslos.</summary>
    public static void RestrictToOwner(string filePath)
    {
        if (OperatingSystem.IsWindows()) return;

        try
        {
            File.SetUnixFileMode(filePath, UnixFileMode.UserRead | UnixFileMode.UserWrite);
        }
        catch (IOException) { }
        catch (UnauthorizedAccessException) { }
    }

    // ─── Auflösung ───

    private static string ResolveConfig()
    {
        if (FromEnvironment("CLAWD_CONFIG_DIR") is { } explicitDir)
            return explicitDir;

        if (OperatingSystem.IsWindows())
            return Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppFolder);

        if (FromEnvironment("XDG_CONFIG_HOME") is { } xdg)
            return Path.Combine(xdg, AppFolder.ToLowerInvariant());

        if (FromEnvironment("HOME") is { } home)
            return Path.Combine(home, ".config", AppFolder.ToLowerInvariant());

        // Ein Dienst ohne HOME. Lieber ein fester, dokumentierter Ort als ein relativer
        // Pfad, der vom Arbeitsverzeichnis abhängt und beim nächsten Start woanders liegt.
        return Path.Combine("/var/lib", AppFolder.ToLowerInvariant());
    }

    private static string ResolveData()
    {
        if (FromEnvironment("CLAWD_DATA_DIR") is { } explicitDir)
            return explicitDir;

        if (OperatingSystem.IsWindows())
            return Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppFolder);

        if (FromEnvironment("XDG_DATA_HOME") is { } xdg)
            return Path.Combine(xdg, AppFolder.ToLowerInvariant());

        if (FromEnvironment("HOME") is { } home)
            return Path.Combine(home, ".local", "share", AppFolder.ToLowerInvariant());

        return Path.Combine("/var/lib", AppFolder.ToLowerInvariant());
    }

    private static string? FromEnvironment(string name)
    {
        var value = Environment.GetEnvironmentVariable(name);
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

    /// <summary>
    /// <c>GetFolderPath</c> kann eine leere Zeichenkette liefern (Dienstkonto ohne
    /// geladenes Profil). <c>Path.Combine("", …)</c> ergäbe dann einen relativen Pfad —
    /// die Datei landete im Arbeitsverzeichnis und wäre beim nächsten Start verschwunden.
    /// </summary>
    private static string Combine(string root, string folder)
        => string.IsNullOrWhiteSpace(root)
            ? Path.Combine(AppContext.BaseDirectory, folder)
            : Path.Combine(root, folder);
}
