using ClawdDotNet.Core.Backup;
using ClawdDotNet.Models;
using ClawdDotNet.Services;
using Microsoft.Extensions.Logging;

namespace ClawdDotNet.UI;

/// <summary>
/// Bedienoberfläche für Sicherung und Wiederherstellung einer Instanz.
///
/// Zwei Entscheidungen prägen das Verhalten:
///
/// Die Passphrase wird nirgends gespeichert. Läge sie neben den Sicherungen, wäre die
/// Verschlüsselung wirkungslos. Deshalb muss sie bei jeder geschützten Sicherung neu
/// eingegeben werden, und die Automatik sichert ohne Zugangsdaten.
///
/// Wiederhergestellt wird standardmäßig in einen NEUEN Ordner, nicht über die laufende
/// Instanz. Diese hält Chatverläufe im Speicher und die Datenbank geöffnet — ein
/// Überschreiben im Betrieb würde teils sofort wieder überschrieben und teils
/// scheitern.
/// </summary>
public sealed partial class BackupPanel : UserControl
{
    private readonly BackupService _service = new();

    private SettingsManager? _settingsManager;
    private ILogger? _logger;
    private string _instanceDir = "";
    private string _instanceName = "";
    private bool _loading;

    public BackupPanel()
    {
        InitializeComponent();
        WireEvents();
    }

    /// <summary>
    /// Aktualisiert die Liste von außen — etwa nachdem die Automatik gesichert hat.
    /// </summary>
    public void RefreshFromOutside()
    {
        if (!IsHandleCreated || IsDisposed)
            return;

        RefreshBackupList();
    }

    /// <summary>Wird vom Hauptfenster nach dem Laden der Instanz aufgerufen.</summary>
    public void Initialize(string instanceDir, string instanceName,
        SettingsManager settingsManager, ILogger logger)
    {
        _instanceDir = instanceDir;
        _instanceName = instanceName;
        _settingsManager = settingsManager;
        _logger = logger;

        LoadSettings();
        UpdateEnabledState();
        RefreshBackupList();
    }

    private void WireEvents()
    {
        btn_browseFolder.Click += OnBrowseFolder;
        btn_createBackup.Click += async (_, _) => await CreateBackupAsync();
        btn_refresh.Click += (_, _) => RefreshBackupList();
        btn_restore.Click += async (_, _) => await RestoreSelectedAsync();
        btn_showInFolder.Click += (_, _) => ShowSelectedInFolder();
        btn_delete.Click += (_, _) => DeleteSelected();

        rb_secretsExclude.CheckedChanged += (_, _) => UpdateEnabledState();
        rb_secretsPassphrase.CheckedChanged += (_, _) => UpdateEnabledState();

        chk_autoEnabled.CheckedChanged += (_, _) => { UpdateEnabledState(); SaveSettings(); };
        dtp_autoTime.ValueChanged += (_, _) => SaveSettings();
        num_keepCount.ValueChanged += (_, _) => SaveSettings();
        txt_targetFolder.Leave += (_, _) => SaveSettings();

        lv_backups.SelectedIndexChanged += (_, _) => UpdateEnabledState();
        lv_backups.DoubleClick += async (_, _) => await RestoreSelectedAsync();
    }

    // ═══════════════════════════════════════════════════
    //  Einstellungen
    // ═══════════════════════════════════════════════════

    private void LoadSettings()
    {
        if (_settingsManager is null) return;

        _loading = true;
        try
        {
            var settings = _settingsManager.AppSettings;

            txt_targetFolder.Text = Path.GetFullPath(settings.BackupDirectory);
            chk_autoEnabled.Checked = settings.AutoBackupEnabled;
            num_keepCount.Value = Math.Clamp(settings.BackupKeepCount, 0, 999);

            dtp_autoTime.Value = TimeSpan.TryParse(settings.AutoBackupTime, out var time)
                ? DateTime.Today.Add(time)
                : DateTime.Today.AddHours(3);
        }
        finally
        {
            _loading = false;
        }
    }

    private void SaveSettings()
    {
        if (_loading || _settingsManager is null) return;

        var settings = _settingsManager.AppSettings;
        settings.BackupDirectory = txt_targetFolder.Text.Trim();
        settings.AutoBackupEnabled = chk_autoEnabled.Checked;
        settings.AutoBackupTime = dtp_autoTime.Value.ToString("HH:mm");
        settings.BackupKeepCount = (int)num_keepCount.Value;

        _settingsManager.Save();
    }

    private void UpdateEnabledState()
    {
        var withPassphrase = rb_secretsPassphrase.Checked;
        label_passphrase.Enabled = withPassphrase;
        txt_passphrase.Enabled = withPassphrase;
        label_passphraseRepeat.Enabled = withPassphrase;
        txt_passphraseRepeat.Enabled = withPassphrase;

        dtp_autoTime.Enabled = chk_autoEnabled.Checked;
        num_keepCount.Enabled = chk_autoEnabled.Checked;

        var hasSelection = lv_backups.SelectedItems.Count > 0;
        btn_restore.Enabled = hasSelection;
        btn_showInFolder.Enabled = hasSelection;
        btn_delete.Enabled = hasSelection;
    }

    // ═══════════════════════════════════════════════════
    //  Sichern
    // ═══════════════════════════════════════════════════

    private void OnBrowseFolder(object? sender, EventArgs e)
    {
        using var dialog = new FolderBrowserDialog
        {
            Description = "Ordner für Sicherungen wählen",
            UseDescriptionForTitle = true,
            SelectedPath = Directory.Exists(txt_targetFolder.Text)
                ? txt_targetFolder.Text
                : Environment.CurrentDirectory
        };

        if (dialog.ShowDialog(this) == DialogResult.OK)
        {
            txt_targetFolder.Text = dialog.SelectedPath;
            SaveSettings();
            RefreshBackupList();
        }
    }

    private async Task CreateBackupAsync()
    {
        if (string.IsNullOrWhiteSpace(_instanceDir))
            return;

        var options = BuildOptions();
        if (options is null)
            return;   // Eingabefehler, bereits gemeldet

        var folder = txt_targetFolder.Text.Trim();
        var file = Path.Combine(folder, BuildFileName());

        btn_createBackup.Enabled = false;
        SetStatus("Sicherung läuft…", SystemColors.GrayText);

        try
        {
            var result = await Task.Run(() => _service.CreateAsync(_instanceDir, file, options));

            SetStatus($"Fertig: {Path.GetFileName(result.ZipPath)} ({FormatSize(result.SizeBytes)})",
                Color.FromArgb(0, 128, 0));

            _logger?.LogInformation("Sicherung erstellt: {Path} ({Size} Bytes)",
                result.ZipPath, result.SizeBytes);

            txt_passphrase.Clear();
            txt_passphraseRepeat.Clear();

            ApplyRotation(folder);
            RefreshBackupList();
        }
        catch (Exception ex)
        {
            SetStatus("Fehlgeschlagen", Color.Firebrick);
            _logger?.LogError(ex, "Sicherung fehlgeschlagen");
            MessageBox.Show(this, $"Die Sicherung ist fehlgeschlagen:\n\n{ex.Message}",
                "Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        finally
        {
            btn_createBackup.Enabled = true;
        }
    }

    /// <summary>Liest die Eingaben. Gibt null zurück, wenn etwas fehlt.</summary>
    private BackupOptions? BuildOptions()
    {
        if (string.IsNullOrWhiteSpace(txt_targetFolder.Text))
        {
            MessageBox.Show(this, "Bitte einen Zielordner angeben.",
                "Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Information);
            return null;
        }

        if (!rb_secretsPassphrase.Checked)
        {
            return new BackupOptions
            {
                Secrets = SecretMode.Exclude,
                IncludeLogs = chk_includeLogs.Checked,
                IncludeChatHistory = chk_includeChatHistory.Checked
            };
        }

        var passphrase = txt_passphrase.Text;

        if (string.IsNullOrEmpty(passphrase))
        {
            MessageBox.Show(this, "Bitte eine Passphrase eingeben.",
                "Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Information);
            txt_passphrase.Focus();
            return null;
        }

        if (passphrase != txt_passphraseRepeat.Text)
        {
            MessageBox.Show(this, "Die beiden Passphrasen stimmen nicht überein.",
                "Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            txt_passphraseRepeat.Focus();
            txt_passphraseRepeat.SelectAll();
            return null;
        }

        // Eine vergessene Passphrase bedeutet unwiederbringlich verlorene Zugangsdaten.
        var answer = MessageBox.Show(this,
            "Ohne diese Passphrase lassen sich die Zugangsdaten in der Sicherung " +
            "nicht wiederherstellen. Es gibt keinen Ersatzweg.\n\n" +
            "Passphrase sicher notiert?",
            "Passphrase bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

        if (answer != DialogResult.Yes)
            return null;

        return new BackupOptions
        {
            Secrets = SecretMode.Passphrase,
            Passphrase = passphrase,
            IncludeLogs = chk_includeLogs.Checked,
            IncludeChatHistory = chk_includeChatHistory.Checked
        };
    }

    private string BuildFileName()
    {
        var safeName = string.Concat(
            (_instanceName.Length > 0 ? _instanceName : "Instanz")
            .Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));

        return $"backup_{safeName}_{DateTime.Now:yyyy-MM-dd_HHmm}.zip";
    }

    // ═══════════════════════════════════════════════════
    //  Liste
    // ═══════════════════════════════════════════════════

    private void RefreshBackupList()
    {
        lv_backups.BeginUpdate();
        lv_backups.Items.Clear();

        try
        {
            var folder = txt_targetFolder.Text.Trim();
            if (!Directory.Exists(folder))
                return;

            var files = new DirectoryInfo(folder)
                .GetFiles("*.zip")
                .OrderByDescending(f => f.LastWriteTime);

            foreach (var file in files)
            {
                var item = new ListViewItem(file.Name) { Tag = file.FullName };
                item.SubItems.Add(file.LastWriteTime.ToString("g"));

                // Das Manifest verrät Instanz und ob Zugangsdaten enthalten sind.
                try
                {
                    var manifest = _service.InspectAsync(file.FullName).GetAwaiter().GetResult();
                    item.SubItems.Add(manifest.InstanceName);
                    item.SubItems.Add(FormatSize(file.Length));
                    item.SubItems.Add(manifest.HasSecrets
                        ? $"ja ({manifest.SecretCount}, Passphrase)"
                        : "nein");
                }
                catch (Exception)
                {
                    // Fremde oder beschädigte Archive trotzdem anzeigen — der Benutzer
                    // soll sehen, was im Ordner liegt.
                    item.SubItems.Add("—");
                    item.SubItems.Add(FormatSize(file.Length));
                    item.SubItems.Add("unlesbar");
                    item.ForeColor = SystemColors.GrayText;
                }

                lv_backups.Items.Add(item);
            }
        }
        finally
        {
            lv_backups.EndUpdate();
            UpdateEnabledState();
        }
    }

    private string? SelectedBackupPath
        => lv_backups.SelectedItems.Count > 0
            ? lv_backups.SelectedItems[0].Tag as string
            : null;

    private void ShowSelectedInFolder()
    {
        if (SelectedBackupPath is not { } path) return;

        ClawdDotNet.Core.Storage.SystemShell.RevealFile(path);
    }

    private void DeleteSelected()
    {
        if (SelectedBackupPath is not { } path) return;

        var answer = MessageBox.Show(this,
            $"Sicherung endgültig löschen?\n\n{Path.GetFileName(path)}",
            "Löschen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

        if (answer != DialogResult.Yes)
            return;

        try
        {
            File.Delete(path);
            RefreshBackupList();
        }
        catch (Exception ex)
        {
            MessageBox.Show(this, $"Konnte nicht gelöscht werden:\n\n{ex.Message}",
                "Löschen", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    // ═══════════════════════════════════════════════════
    //  Wiederherstellen
    // ═══════════════════════════════════════════════════

    private async Task RestoreSelectedAsync()
    {
        if (SelectedBackupPath is not { } zipPath) return;

        BackupManifest manifest;
        try
        {
            manifest = await _service.InspectAsync(zipPath);
        }
        catch (Exception ex)
        {
            MessageBox.Show(this, $"Die Sicherung ist nicht lesbar:\n\n{ex.Message}",
                "Wiederherstellen", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

        using var dialog = new frm_RestoreBackup(manifest, SuggestRestoreTarget(manifest));
        if (dialog.ShowDialog(this) != DialogResult.OK)
            return;

        var options = new RestoreOptions
        {
            Passphrase = dialog.Passphrase,
            Overwrite = dialog.Overwrite
        };

        try
        {
            // Erst ohne zu schreiben prüfen — Prüfsummen und Konflikte.
            var preview = await Task.Run(() =>
                _service.RestoreAsync(zipPath, dialog.TargetDirectory,
                    options with { DryRun = true, Overwrite = true }));

            if (preview.WouldOverwrite.Count > 0 && !dialog.Overwrite)
            {
                var answer = MessageBox.Show(this,
                    $"Im Zielordner liegen bereits {preview.WouldOverwrite.Count} der enthaltenen " +
                    "Dateien.\n\nVorhandene Dateien überschreiben?",
                    "Wiederherstellen", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);

                if (answer == DialogResult.Cancel)
                    return;

                options = options with { Overwrite = answer == DialogResult.Yes };
            }

            var result = await Task.Run(() =>
                _service.RestoreAsync(zipPath, dialog.TargetDirectory, options));

            _logger?.LogInformation("Wiederherstellung nach {Target}: {Written} Dateien, {Skipped} übersprungen",
                dialog.TargetDirectory, result.Written.Count, result.Skipped.Count);

            var summary = $"{result.Written.Count} Datei(en) wiederhergestellt nach:\n{dialog.TargetDirectory}";
            if (result.Skipped.Count > 0)
                summary += $"\n\n{result.Skipped.Count} vorhandene Datei(en) blieben unangetastet.";

            if (!manifest.HasSecrets && manifest.SecretCount > 0)
                summary += "\n\nHinweis: Diese Sicherung enthält keine Zugangsdaten. " +
                           "API-Schlüssel und Passwörter müssen neu eingetragen werden.";

            summary += "\n\nDie wiederhergestellte Instanz wird erst nach einem Neustart " +
                       "der Anwendung sichtbar.";

            MessageBox.Show(this, summary, "Wiederherstellen",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Wiederherstellung fehlgeschlagen");
            MessageBox.Show(this, $"Die Wiederherstellung ist fehlgeschlagen:\n\n{ex.Message}",
                "Wiederherstellen", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    /// <summary>
    /// Schlägt einen neuen Ordner neben der Instanz vor, nicht die laufende Instanz
    /// selbst — diese hält Dateien geöffnet und würde Teile sofort überschreiben.
    /// </summary>
    private string SuggestRestoreTarget(BackupManifest manifest)
    {
        var parent = Directory.GetParent(_instanceDir)?.FullName ?? _instanceDir;
        var name = string.IsNullOrWhiteSpace(manifest.InstanceName)
            ? "Instanz"
            : manifest.InstanceName;

        var safeName = string.Concat(
            name.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));

        return Path.Combine(parent, $"Instance-{safeName}_wiederhergestellt_{DateTime.Now:yyyyMMdd_HHmm}");
    }

    // ═══════════════════════════════════════════════════
    //  Rotation
    // ═══════════════════════════════════════════════════

    /// <summary>Entfernt die ältesten Sicherungen dieser Instanz über der Aufbewahrungszahl.</summary>
    private void ApplyRotation(string folder)
    {
        var keep = (int)num_keepCount.Value;
        if (keep <= 0 || !Directory.Exists(folder))
            return;

        try
        {
            var prefix = $"backup_{_instanceName}_";

            var obsolete = new DirectoryInfo(folder)
                .GetFiles("*.zip")
                .Where(f => f.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                .OrderByDescending(f => f.LastWriteTime)
                .Skip(keep)
                .ToList();

            foreach (var file in obsolete)
            {
                file.Delete();
                _logger?.LogInformation("Alte Sicherung entfernt: {Name}", file.Name);
            }
        }
        catch (Exception ex)
        {
            // Aufräumen darf eine erfolgreiche Sicherung nicht nachträglich entwerten.
            _logger?.LogWarning(ex, "Rotation der Sicherungen fehlgeschlagen");
        }
    }

    // ═══════════════════════════════════════════════════
    //  Kleinkram
    // ═══════════════════════════════════════════════════

    private void SetStatus(string text, Color color)
    {
        label_createStatus.Text = text;
        label_createStatus.ForeColor = color;
    }

    private static string FormatSize(long bytes) => bytes switch
    {
        < 1024 => $"{bytes} B",
        < 1024 * 1024 => $"{bytes / 1024.0:F1} KB",
        < 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:F1} MB",
        _ => $"{bytes / 1024.0 / 1024 / 1024:F2} GB"
    };
}
