using System.Text.Json;
using ClawdDotNet.Core.Security;
using ClawdDotNet.Core.Storage;

namespace ClawdDotNet.App.Settings;

/// <summary>
/// Lädt und speichert die anwendungsweiten Einstellungen.
///
/// <para><b>Ablageort.</b> Bis zur Linux-Portierung lag <c>Settings.json</c> neben der
/// Programmdatei. Unter Windows in einem Benutzerverzeichnis ging das; unter Linux liegt
/// die Anwendung in <c>/opt</c> oder <c>/usr/lib</c> und ist für den Dienstbenutzer nicht
/// beschreibbar. Jetzt entscheidet <see cref="AppPaths.ConfigDirectory"/> — XDG unter
/// Linux, <c>%APPDATA%</c> unter Windows, per <c>CLAWD_CONFIG_DIR</c> überschreibbar.</para>
///
/// <para><b>Kein Migrationspfad.</b> Bewusst: Zum Zeitpunkt der Umstellung lief noch
/// keine Installation produktiv. Eine bestehende <c>Settings.json</c> neben der
/// Programmdatei wird also <em>nicht</em> übernommen — der Ort wechselt einmal sauber,
/// statt eine Ausweichlogik zu hinterlassen, die niemand mehr anfasst.</para>
///
/// <para><b>Keine Meldungsfenster.</b> Diese Schicht kennt keine Oberfläche. Ein
/// Speicherfehler kommt als <see cref="SettingsPersistenceException"/> heraus; ob daraus
/// ein Dialog, ein Logeintrag oder ein Rückgabewert wird, entscheidet der Aufrufer.</para>
/// </summary>
public sealed class SettingsManager
{
    private const string SettingsFileName = "Settings.json";

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        WriteIndented = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
        AllowTrailingCommas = true,
        PropertyNameCaseInsensitive = true
    };

    private readonly string _settingsPath;

    public AppSettings AppSettings { get; private set; } = new();

    /// <summary>Der Ort der Einstellungsdatei — für Meldungen und Diagnose.</summary>
    public string SettingsPath => _settingsPath;

    public SettingsManager(string? basePath = null)
    {
        var dir = basePath ?? AppPaths.ConfigDirectory;
        _settingsPath = Path.Combine(dir, SettingsFileName);
    }

    public void Load()
    {
        if (!File.Exists(_settingsPath))
        {
            AppSettings = new AppSettings();
            Save(); // Vorgaben festschreiben, damit der Ort sichtbar wird
            return;
        }

        try
        {
            var json = AtomicFile.ReadAllText(_settingsPath);
            AppSettings = JsonSerializer.Deserialize<AppSettings>(json, JsonOptions)
                          ?? new AppSettings();
        }
        catch (Exception ex) when (ex is JsonException or IOException)
        {
            // Eine unlesbare Datei darf den Start nicht verhindern — mit Vorgaben
            // weiterzumachen ist besser, als gar nicht zu starten.
            AppSettings = new AppSettings();
            return;
        }

        // Geheimnisse liegen in der Datei verschlüsselt und werden zur Laufzeit im
        // Klartext gehalten.
        AppSettings.LicenseKey = TryUnprotect(AppSettings.LicenseKey);
        AppSettings.DeploymentcenterToken = TryUnprotect(AppSettings.DeploymentcenterToken);
    }

    /// <summary>
    /// Entschlüsselt einen Wert; bei Nicht-Lesbarkeit (anderer Benutzer, anderer Rechner,
    /// Umzug von Windows) leer, damit der Nutzer ihn neu eintragen kann statt einen
    /// unbrauchbaren Wert an eine Gegenstelle zu schicken.
    /// </summary>
    private static string TryUnprotect(string value)
    {
        try { return SecretProtector.Unprotect(value) ?? ""; }
        catch (SecretProtectionException) { return ""; }
    }

    /// <exception cref="SettingsPersistenceException">Wenn die Datei nicht geschrieben werden kann.</exception>
    public void Save()
    {
        // Nur zum Schreiben verschlüsseln; die laufende Instanz braucht Klartext.
        var plainLicenseKey = AppSettings.LicenseKey;
        var plainDeploymentcenterToken = AppSettings.DeploymentcenterToken;

        try
        {
            AppSettings.LicenseKey = SecretProtector.Protect(plainLicenseKey) ?? "";
            AppSettings.DeploymentcenterToken = SecretProtector.Protect(plainDeploymentcenterToken) ?? "";

            AppPaths.EnsureDirectory(Path.GetDirectoryName(_settingsPath)!);

            var json = JsonSerializer.Serialize(AppSettings, JsonOptions);
            AtomicFile.WriteAllText(_settingsPath, json);
            AppPaths.RestrictToOwner(_settingsPath);
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException
                                       or SecretProtectionException)
        {
            throw new SettingsPersistenceException(
                $"Die Einstellungen konnten nicht nach {_settingsPath} geschrieben werden: {ex.Message}",
                ex);
        }
        finally
        {
            AppSettings.LicenseKey = plainLicenseKey;
            AppSettings.DeploymentcenterToken = plainDeploymentcenterToken;
        }
    }
}

public sealed class SettingsPersistenceException(string message, Exception inner)
    : Exception(message, inner);
