using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.Platform;
using Avalonia.Threading;
using PhotoboothUploader.Models;
using PhotoboothUploader.Services;

namespace PhotoboothUploader;

public partial class MainWindow : Window
{
    private const string DefaultBaseUrl = "https://fotospiel.app";
    private PhotoboothConnectClient _client;
    private readonly SettingsStore _settingsStore = new();
    private readonly UploadService _uploadService = new();
    private PhotoboothSettings _settings;
    private FileSystemWatcher? _watcher;
    private readonly Dictionary<string, UploadItem> _uploadsByPath = new(StringComparer.OrdinalIgnoreCase);
    private readonly HashSet<string> _failedPaths = new(StringComparer.OrdinalIgnoreCase);
    private readonly string _userAgent;
    private int _queuedCount;
    private int _uploadingCount;
    private int _failedCount;
    private DateTimeOffset? _lastSuccessAt;
    private bool _advancedVisible;
    private readonly DispatcherTimer _liveTimer = new();
    private readonly List<string> _logBuffer = new();

    public ObservableCollection<UploadItem> RecentUploads { get; } = new();

    public MainWindow()
    {
        InitializeComponent();
        _settings = _settingsStore.Load();
        EnsureSettingsCollections();
        _settings.BaseUrl = NormalizeBaseUrl(_settings.BaseUrl) ?? DefaultBaseUrl;
        if (_settings.MaxConcurrentUploads <= 0)
        {
            _settings.MaxConcurrentUploads = 2;
        }
        _userAgent = $"FotospielPhotoboothUploader/{GetAppVersion()}";
        _client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
        _uploadService.Configure(_userAgent);
        _settingsStore.Save(_settings);
        DataContext = this;
        _liveTimer.Interval = TimeSpan.FromSeconds(30);
        _liveTimer.Tick += (_, _) => UpdateLiveStatus();
        _liveTimer.Start();
        Opened += OnWindowOpened;
        Closing += OnWindowClosing;
        ApplySettings();
    }

    private async void ConnectButton_Click(object? sender, RoutedEventArgs e)
    {
        var code = (CodeBox.Text ?? string.Empty).Trim();

        if (!IsValidCode(code))
        {
            StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben.";
            return;
        }

        ConnectButton.IsEnabled = false;
        ReconnectButton.IsEnabled = false;
        StatusText.Text = "Verbinde...";

        await RedeemCodeAsync(code);

        ConnectButton.IsEnabled = true;
        ReconnectButton.IsEnabled = true;
    }

    private async void ReconnectButton_Click(object? sender, RoutedEventArgs e)
    {
        var code = (CodeBox.Text ?? string.Empty).Trim();

        if (!IsValidCode(code))
        {
            StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben.";
            return;
        }

        ReconnectButton.IsEnabled = false;
        StatusText.Text = "Verbindung erneuern...";

        await RedeemCodeAsync(code);

        ReconnectButton.IsEnabled = true;
    }

    private async Task RedeemCodeAsync(string code)
    {
        var normalizedBaseUrl = NormalizeBaseUrl(_settings.BaseUrl);
        if (normalizedBaseUrl is null)
        {
            UpdateStatus("Ungültige Basis-URL. Bitte Support kontaktieren.");
            SetLastError("Ungültige Basis-URL.");
            return;
        }

        if (!string.Equals(normalizedBaseUrl, _settings.BaseUrl, StringComparison.OrdinalIgnoreCase))
        {
            _settings.BaseUrl = normalizedBaseUrl;
            _client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
        }

        var response = await _client.RedeemAsync(code);

        if (response.Data is null)
        {
            StatusText.Text = response.Message ?? "Verbindung fehlgeschlagen.";
            SetLastError(response.Message ?? "Verbindung fehlgeschlagen.");
            return;
        }

        ClearLastError();
        _settings.UploadUrl = ResolveUploadUrl(response.Data.UploadUrl);
        _settings.Username = response.Data.Username;
        _settings.Password = response.Data.Password;
        _settings.ResponseFormat = response.Data.ResponseFormat;
        _settings.EventName = response.Data.EventName;
        _settings.ConnectExpiresAt = response.Data.ExpiresAt;
        _settingsStore.Save(_settings);

        StatusText.Text = "Verbunden. Upload bereit.";
        AppendLog("Verbunden mit Event.");
        PickFolderButton.IsEnabled = true;
        TestUploadButton.IsEnabled = true;
        ReconnectButton.IsEnabled = true;
        UpdateAdvancedLockState();
        UpdateDiagnostics();
        StartUploadPipelineIfReady();
    }

    private async void PickFolderButton_Click(object? sender, RoutedEventArgs e)
    {
        var options = new FolderPickerOpenOptions
        {
            Title = "Upload-Ordner auswählen",
            AllowMultiple = false,
        };

        var folders = await StorageProvider.OpenFolderPickerAsync(options);
        var folder = folders.FirstOrDefault();
        var localPath = folder?.TryGetLocalPath();

        if (string.IsNullOrWhiteSpace(localPath))
        {
            return;
        }

        _settings.WatchFolder = localPath;
        _settingsStore.Save(_settings);

        FolderText.Text = localPath;
        UpdateFolderHealth();
        StartUploadPipelineIfReady();
    }

    private void DslrBoothPresetButton_Click(object? sender, RoutedEventArgs e)
    {
        ApplyPresetFolder(GetDslrBoothFolder());
    }

    private void SparkboothPresetButton_Click(object? sender, RoutedEventArgs e)
    {
        ApplyPresetFolder(GetSparkboothFolder());
    }

    private void ApplySettings()
    {
        if (!string.IsNullOrWhiteSpace(_settings.WatchFolder))
        {
            FolderText.Text = _settings.WatchFolder;
        }

        BaseUrlBox.Text = _settings.BaseUrl ?? DefaultBaseUrl;
        MaxUploadsBox.Text = _settings.MaxConcurrentUploads.ToString();
        UploadTempoBox.SelectedIndex = ResolveUploadTempoIndex(_settings.UploadDelayMs);
        IncludePatternsBox.Text = _settings.IncludePatterns ?? string.Empty;
        ExcludePatternsBox.Text = _settings.ExcludePatterns ?? string.Empty;
        ResponseFormatBox.SelectedIndex = ResolveResponseFormatIndex(_settings.ResponseFormat);
        ManualUploadUrlBox.Text = _settings.UploadUrl ?? string.Empty;
        ManualUsernameBox.Text = _settings.Username ?? string.Empty;
        ManualPasswordBox.Text = string.Empty;
        SettingsUnlockToggle.IsChecked = false;

        if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
        {
            StatusText.Text = "Verbunden. Upload bereit.";
            PickFolderButton.IsEnabled = true;
            TestUploadButton.IsEnabled = true;
            ReconnectButton.IsEnabled = true;
            StartUploadPipelineIfReady();
        }

        UpdateCountersText();
        UpdateFolderHealth();
        UpdateDiagnostics();
        UpdateSteps();
        RefreshProfiles();
        UpdateAdvancedLockState();
        UpdatePresetButtons();
    }

    private void OnWindowOpened(object? sender, EventArgs e)
    {
        ApplyWindowSize();
    }

    private void OnWindowClosing(object? sender, WindowClosingEventArgs e)
    {
        _settings.WindowWidth = Width;
        _settings.WindowHeight = Height;
        _settingsStore.Save(_settings);
    }

    private void ApplyWindowSize()
    {
        if (_settings.WindowWidth > 0)
        {
            Width = Math.Max(MinWidth, _settings.WindowWidth);
        }

        if (_settings.WindowHeight > 0)
        {
            Height = Math.Max(MinHeight, _settings.WindowHeight);
        }
    }

    private void StartUploadPipelineIfReady()
    {
        if (string.IsNullOrWhiteSpace(_settings.UploadUrl) || string.IsNullOrWhiteSpace(_settings.WatchFolder))
        {
            UpdateSteps();
            return;
        }

        ResetCounters();
        _uploadService.Start(_settings, OnQueued, OnUploading, OnSuccess, OnFailure);
        StartWatcher(_settings.WatchFolder);
        RestorePendingUploads();
        UpdateSteps();
    }

    private void StartWatcher(string folder)
    {
        _watcher?.Dispose();

        _watcher = new FileSystemWatcher(folder)
        {
            IncludeSubdirectories = false,
            EnableRaisingEvents = true,
        };

        _watcher.Created += OnFileChanged;
        _watcher.Changed += OnFileChanged;
        _watcher.Renamed += OnFileRenamed;
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        if (!IsSupportedImage(e.FullPath))
        {
            return;
        }

        if (ShouldSkipUpload(e.FullPath))
        {
            return;
        }

        RecordLastSeen(e.FullPath);
        _uploadService.Enqueue(e.FullPath, OnQueued);
    }

    private void OnFileRenamed(object sender, RenamedEventArgs e)
    {
        if (!IsSupportedImage(e.FullPath))
        {
            return;
        }

        if (ShouldSkipUpload(e.FullPath))
        {
            return;
        }

        RecordLastSeen(e.FullPath);
        _uploadService.Enqueue(e.FullPath, OnQueued);
    }

    private bool IsSupportedImage(string path)
    {
        var extension = Path.GetExtension(path)?.ToLowerInvariant();

        return extension is ".jpg" or ".jpeg" or ".png" or ".webp";
    }

    private void UpdateStatus(string message)
    {
        Dispatcher.UIThread.Post(() => StatusText.Text = message);
    }

    private void OnQueued(string path)
    {
        Interlocked.Increment(ref _queuedCount);
        UpdateUpload(path, UploadStatus.Queued);
        AddPendingUpload(path);
        UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false);
        AppendLog($"Wartet: {Path.GetFileName(path)}");
        UpdateCountersText();
    }

    private void OnUploading(string path)
    {
        Interlocked.Decrement(ref _queuedCount);
        Interlocked.Increment(ref _uploadingCount);
        UpdateUpload(path, UploadStatus.Uploading);
        UpdateStatusIfAllowed($"Upload läuft: {Path.GetFileName(path)}", false);
        UpdateCountersText();
    }

    private void OnSuccess(string path)
    {
        _failedPaths.Remove(path);
        Interlocked.Decrement(ref _uploadingCount);
        _lastSuccessAt = DateTimeOffset.Now;
        UpdateUpload(path, UploadStatus.Success);
        RemovePendingUpload(path);
        MarkUploaded(path);
        UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false);
        AppendLog($"Hochgeladen: {Path.GetFileName(path)}");
        UpdateCountersText();
        UpdateLiveStatus();
    }

    private void OnFailure(string path, string message)
    {
        _failedPaths.Add(path);
        Interlocked.Decrement(ref _uploadingCount);
        Interlocked.Increment(ref _failedCount);
        UpdateUpload(path, UploadStatus.Failed);
        RemovePendingUpload(path);
        UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true);
        SetLastError($"{Path.GetFileName(path)} – {message}");
        AppendLog($"Fehlgeschlagen: {Path.GetFileName(path)} – {message}");
        UpdateRetryButton();
        UpdateCountersText();
    }

    private void UpdateUpload(string path, UploadStatus status)
    {
        Dispatcher.UIThread.Post(() =>
        {
            if (!_uploadsByPath.TryGetValue(path, out var item))
            {
                item = new UploadItem(path);
                _uploadsByPath[path] = item;
                RecentUploads.Insert(0, item);
            }

            item.Status = status;
            LastUploadText.Text = status == UploadStatus.Success
                ? $"Letzter Upload: {item.UpdatedLabel}"
                : LastUploadText.Text;

            while (RecentUploads.Count > 3)
            {
                var last = RecentUploads[^1];
                _uploadsByPath.Remove(last.Path);
                RecentUploads.RemoveAt(RecentUploads.Count - 1);
            }

            UpdateRetryButton();
        });
    }

    private void UpdateStatusIfAllowed(string message, bool important)
    {
        var quiet = QuietToggle?.IsChecked ?? false;

        if (quiet && !important)
        {
            return;
        }

        UpdateStatus(message);
    }

    private void UpdateRetryButton()
    {
        RetryFailedButton.IsEnabled = _failedPaths.Count > 0;
        ClearFailedButton.IsEnabled = _failedPaths.Count > 0;
    }

    private void RetryFailedButton_Click(object? sender, RoutedEventArgs e)
    {
        if (_failedPaths.Count == 0)
        {
            return;
        }

        var retried = _failedPaths.Count;
        foreach (var path in _failedPaths.ToList())
        {
            _uploadService.Enqueue(path, OnQueued);
        }

        _failedPaths.Clear();
        Interlocked.Add(ref _failedCount, -retried);
        Interlocked.Add(ref _queuedCount, retried);
        UpdateRetryButton();
        UpdateCountersText();
    }

    private void ClearFailedButton_Click(object? sender, RoutedEventArgs e)
    {
        if (_failedPaths.Count == 0)
        {
            return;
        }

        var cleared = _failedPaths.Count;
        _failedPaths.Clear();
        Interlocked.Add(ref _failedCount, -cleared);
        UpdateRetryButton();
        UpdateCountersText();
        UpdateStatusIfAllowed("Fehlerliste geleert.", false);
    }

    private void UpdateSteps()
    {
        var hasCode = !string.IsNullOrWhiteSpace(_settings.UploadUrl);
        var hasFolder = !string.IsNullOrWhiteSpace(_settings.WatchFolder);
        var ready = hasCode && hasFolder;

        StepCodeText.Text = hasCode ? "1. Code eingeben ✓" : "1. Code eingeben";
        StepFolderText.Text = hasFolder ? "2. Upload-Ordner wählen ✓" : "2. Upload-Ordner wählen";
        StepReadyText.Text = ready ? "3. Upload läuft ✓" : "3. Upload läuft";
    }

    private void CodeBox_TextChanged(object? sender, TextChangedEventArgs e)
    {
        var code = (CodeBox.Text ?? string.Empty).Trim();
        var enabled = IsValidCode(code);
        ReconnectButton.IsEnabled = enabled;
    }

    private void SettingsUnlockToggle_Changed(object? sender, RoutedEventArgs e)
    {
        UpdateAdvancedLockState();
    }

    private void RestorePendingUploads()
    {
        EnsureSettingsCollections();

        if (_settings.PendingUploads.Count == 0)
        {
            return;
        }

        var pending = _settings.PendingUploads.ToList();
        var changed = false;

        foreach (var path in pending)
        {
            if (string.IsNullOrWhiteSpace(path) || !File.Exists(path) || ShouldSkipUpload(path))
            {
                _settings.PendingUploads.Remove(path);
                changed = true;
                continue;
            }

            _uploadService.Enqueue(path, OnQueued);
        }

        if (changed)
        {
            _settingsStore.Save(_settings);
        }
    }

    private bool ShouldSkipUpload(string path)
    {
        var fileName = Path.GetFileName(path);
        if (!MatchesIncludePatterns(fileName))
        {
            return true;
        }

        if (MatchesExcludePatterns(fileName))
        {
            return true;
        }

        var signature = GetUploadSignature(path);
        if (signature is null)
        {
            return true;
        }

        return _settings.UploadedFiles.TryGetValue(path, out var recorded) && recorded == signature;
    }

    private string? GetUploadSignature(string path)
    {
        if (!File.Exists(path))
        {
            return null;
        }

        var info = new FileInfo(path);
        return $"{info.Length}:{info.LastWriteTimeUtc.Ticks}";
    }

    private void AddPendingUpload(string path)
    {
        EnsureSettingsCollections();

        if (!_settings.PendingUploads.Contains(path))
        {
            _settings.PendingUploads.Add(path);
            _settingsStore.Save(_settings);
        }
    }

    private void RemovePendingUpload(string path)
    {
        EnsureSettingsCollections();

        if (_settings.PendingUploads.Remove(path))
        {
            _settingsStore.Save(_settings);
        }
    }

    private void MarkUploaded(string path)
    {
        var signature = GetUploadSignature(path);
        if (signature is null)
        {
            return;
        }

        EnsureSettingsCollections();
        _settings.UploadedFiles[path] = signature;
        _settingsStore.Save(_settings);
    }

    private void TitleText_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e)
    {
        if (e.ClickCount < 2)
        {
            return;
        }

        _advancedVisible = !_advancedVisible;
        AdvancedPanel.IsVisible = _advancedVisible;
    }

    private void SaveAdvancedButton_Click(object? sender, RoutedEventArgs e)
    {
        var normalizedBaseUrl = NormalizeBaseUrl(BaseUrlBox.Text);
        if (normalizedBaseUrl is null)
        {
            UpdateStatus("Ungültige Basis-URL.");
            SetLastError("Ungültige Basis-URL.");
            return;
        }

        if (!int.TryParse(MaxUploadsBox.Text, out var maxUploads) || maxUploads < 1 || maxUploads > 5)
        {
            UpdateStatus("Max. parallele Uploads muss zwischen 1 und 5 liegen.");
            SetLastError("Ungültige Parallel-Uploads.");
            return;
        }

        var manualUploadUrl = (ManualUploadUrlBox.Text ?? string.Empty).Trim();
        var manualUsername = (ManualUsernameBox.Text ?? string.Empty).Trim();
        var manualPassword = (ManualPasswordBox.Text ?? string.Empty).Trim();

        _settings.BaseUrl = normalizedBaseUrl;
        _settings.MaxConcurrentUploads = maxUploads;
        _settings.UploadDelayMs = ResolveUploadDelay(UploadTempoBox.SelectedIndex);
        _settings.IncludePatterns = NormalizePatternInput(IncludePatternsBox.Text);
        _settings.ExcludePatterns = NormalizePatternInput(ExcludePatternsBox.Text);
        _settings.ResponseFormat = ResolveResponseFormat(ResponseFormatBox.SelectedIndex);

        if (!string.IsNullOrWhiteSpace(manualUploadUrl))
        {
            _settings.UploadUrl = ResolveUploadUrl(manualUploadUrl);
        }

        if (!string.IsNullOrWhiteSpace(manualUsername))
        {
            _settings.Username = manualUsername;
        }

        if (!string.IsNullOrWhiteSpace(manualPassword))
        {
            _settings.Password = manualPassword;
        }

        _settingsStore.Save(_settings);

        _client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
        _uploadService.Configure(_userAgent);
        UpdateDiagnostics();
        UpdateFolderHealth();
        RestartUploadPipeline();
        UpdateSteps();
        UpdateAdvancedLockState();

        if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
        {
            StatusText.Text = "Verbunden. Upload bereit.";
            PickFolderButton.IsEnabled = true;
            TestUploadButton.IsEnabled = true;
            ReconnectButton.IsEnabled = true;
        }
        UpdateStatus("Einstellungen gespeichert.");
    }

    private async void TestConnectionButton_Click(object? sender, RoutedEventArgs e)
    {
        var targetUrl = ResolveTestUrl();
        if (targetUrl is null)
        {
            UpdateStatus("Bitte eine gültige Upload-URL oder Basis-URL speichern.");
            return;
        }

        UpdateStatus("Verbindung wird getestet...");

        try
        {
            using var client = new HttpClient
            {
                Timeout = TimeSpan.FromSeconds(8),
            };
            client.DefaultRequestHeaders.UserAgent.ParseAdd(_userAgent);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            using var request = new HttpRequestMessage(HttpMethod.Head, targetUrl);
            using var response = await client.SendAsync(request);
            var status = $"{(int)response.StatusCode} {response.ReasonPhrase}".Trim();
            UpdateStatus($"Server erreichbar ({status}).");
        }
        catch (TaskCanceledException)
        {
            UpdateStatus("Verbindungstest: Zeitüberschreitung.");
        }
        catch (HttpRequestException)
        {
            UpdateStatus("Verbindungstest: Netzwerkfehler.");
        }
    }

    private async void TestUploadButton_Click(object? sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(_settings.UploadUrl))
        {
            UpdateStatus("Bitte zuerst verbinden.");
            return;
        }

        try
        {
            var tempPath = await CreateSampleUploadAsync();
            _uploadService.Enqueue(tempPath, OnQueued);
            UpdateStatusIfAllowed("Test-Upload hinzugefügt.", false);
        }
        catch
        {
            UpdateStatus("Test-Upload konnte nicht erstellt werden.");
            SetLastError("Test-Upload konnte nicht erstellt werden.");
        }
    }

    private async Task<string> CreateSampleUploadAsync()
    {
        var uri = new Uri("avares://PhotoboothUploader/Assets/sample-upload.png");
        await using var source = AssetLoader.Open(uri);
        var tempPath = Path.Combine(Path.GetTempPath(), $"fotospiel-test-{DateTimeOffset.Now:yyyyMMddHHmmss}.png");
        await using var target = File.Create(tempPath);
        await source.CopyToAsync(target);
        return tempPath;
    }

    private void ResetCounters()
    {
        Interlocked.Exchange(ref _queuedCount, 0);
        Interlocked.Exchange(ref _uploadingCount, 0);
        Interlocked.Exchange(ref _failedCount, 0);
        _failedPaths.Clear();
        _lastSuccessAt = null;
        UpdateCountersText();
        UpdateRetryButton();
        UpdateLiveStatus();
    }

    private void RestartUploadPipeline()
    {
        _uploadService.Stop();
        StartUploadPipelineIfReady();
    }

    private void UpdateFolderHealth()
    {
        var folder = _settings.WatchFolder;
        if (string.IsNullOrWhiteSpace(folder))
        {
            FolderHealthText.Text = "Ordner: —";
            DiskFreeText.Text = "Freier Speicher: —";
            LastSeenText.Text = "Letzte Datei: —";
            return;
        }

        if (!Directory.Exists(folder))
        {
            FolderHealthText.Text = "Ordner: fehlt";
            DiskFreeText.Text = "Freier Speicher: —";
            LastSeenText.Text = "Letzte Datei: —";
            return;
        }

        try
        {
            _ = Directory.EnumerateFileSystemEntries(folder).FirstOrDefault();
            FolderHealthText.Text = CanWriteToFolder(folder) ? "Ordner: OK (schreibbar)" : "Ordner: OK (nur lesen)";
        }
        catch (UnauthorizedAccessException)
        {
            FolderHealthText.Text = "Ordner: Keine Berechtigung";
        }
        catch
        {
            FolderHealthText.Text = "Ordner: Fehler";
        }

        DiskFreeText.Text = FormatDiskFree(folder);
        LastSeenText.Text = FormatLastSeen();
    }

    private void UpdateLiveStatus()
    {
        Dispatcher.UIThread.Post(() =>
        {
            if (_lastSuccessAt is null)
            {
                LiveStatusText.Text = "Live: —";
                UpdateDiagnostics();
                return;
            }

            var age = DateTimeOffset.Now - _lastSuccessAt.Value;
            var isLive = age <= TimeSpan.FromMinutes(5);
            var label = isLive ? "Live: Ja" : "Live: Nein";
            LiveStatusText.Text = $"{label} (letzter Upload {FormatRelativeAge(age)})";
            UpdateDiagnostics();
        });
    }

    private static string FormatRelativeAge(TimeSpan age)
    {
        if (age.TotalMinutes < 1)
        {
            return "gerade eben";
        }

        if (age.TotalHours < 1)
        {
            return $"vor {Math.Round(age.TotalMinutes)} min";
        }

        if (age.TotalDays < 1)
        {
            return $"vor {Math.Round(age.TotalHours)} h";
        }

        return $"vor {Math.Round(age.TotalDays)} d";
    }

    private static string FormatDiskFree(string folder)
    {
        var root = Path.GetPathRoot(folder);
        if (string.IsNullOrWhiteSpace(root))
        {
            return "Freier Speicher: —";
        }

        var drive = new DriveInfo(root);
        if (!drive.IsReady)
        {
            return "Freier Speicher: —";
        }

        var freeGb = drive.AvailableFreeSpace / (1024d * 1024d * 1024d);
        var label = freeGb < 5 ? "niedrig" : "ok";
        return $"Freier Speicher: {freeGb:0.0} GB ({label})";
    }

    private string? ResolveUploadUrl(string? uploadUrl)
    {
        if (string.IsNullOrWhiteSpace(uploadUrl))
        {
            return uploadUrl;
        }

        if (Uri.TryCreate(uploadUrl, UriKind.Absolute, out _))
        {
            return uploadUrl;
        }

        var baseUri = new Uri(_settings.BaseUrl ?? DefaultBaseUrl, UriKind.Absolute);
        return new Uri(baseUri, uploadUrl).ToString();
    }

    private static string? NormalizeBaseUrl(string? baseUrl)
    {
        if (string.IsNullOrWhiteSpace(baseUrl))
        {
            return null;
        }

        var trimmed = baseUrl.Trim();

        if (Uri.TryCreate(trimmed, UriKind.Absolute, out var absolute))
        {
            return absolute.GetLeftPart(UriPartial.Authority);
        }

        if (Uri.TryCreate($"https://{trimmed}", UriKind.Absolute, out absolute))
        {
            return absolute.GetLeftPart(UriPartial.Authority);
        }

        return null;
    }

    private void UpdateCountersText()
    {
        Dispatcher.UIThread.Post(() =>
        {
            var queued = Volatile.Read(ref _queuedCount);
            var uploading = Volatile.Read(ref _uploadingCount);
            var failed = Volatile.Read(ref _failedCount);
            QueueStatusText.Text = $"Warteschlange: {Math.Max(0, queued)} · Läuft: {Math.Max(0, uploading)} · Fehlgeschlagen: {Math.Max(0, failed)}";
        });
    }

    private void UpdateDiagnostics()
    {
        Dispatcher.UIThread.Post(() =>
        {
            EventNameText.Text = $"Event: {_settings.EventName ?? "—"}";
            BaseUrlText.Text = $"Basis-URL: {_settings.BaseUrl ?? "—"}";
            VersionText.Text = $"App-Version: {GetAppVersion()}";
            ConnectExpiryText.Text = FormatConnectExpiry();
            LastErrorText.Text = $"Letzter Fehler: {FormatLastError()}";
        });
    }

    private void SetLastError(string message)
    {
        _settings.LastError = message;
        _settings.LastErrorAt = DateTimeOffset.Now.ToString("O");
        _settingsStore.Save(_settings);
        UpdateDiagnostics();
    }

    private void ClearLastError()
    {
        _settings.LastError = null;
        _settings.LastErrorAt = null;
        _settingsStore.Save(_settings);
        UpdateDiagnostics();
    }

    private string FormatLastError()
    {
        if (string.IsNullOrWhiteSpace(_settings.LastError))
        {
            return "—";
        }

        if (DateTimeOffset.TryParse(_settings.LastErrorAt, out var timestamp))
        {
            return $"{timestamp:dd.MM.yyyy HH:mm} – {_settings.LastError}";
        }

        return _settings.LastError;
    }

    private static string GetAppVersion()
    {
        var version = Assembly.GetExecutingAssembly().GetName().Version;
        return version is null ? "0.0.0" : version.ToString();
    }

    private static bool IsValidCode(string code)
    {
        return code.Length == 6 && code.All(ch => ch is >= '0' and <= '9');
    }

    private void EnsureSettingsCollections()
    {
        _settings.PendingUploads ??= new List<string>();
        _settings.UploadedFiles ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        _settings.Profiles ??= new List<PhotoboothProfile>();
    }

    private void RecordLastSeen(string path)
    {
        _settings.LastSeenFile = Path.GetFileName(path);
        _settings.LastSeenAt = DateTimeOffset.Now.ToString("O");
        _settingsStore.Save(_settings);
        UpdateFolderHealth();
    }

    private bool CanWriteToFolder(string folder)
    {
        try
        {
            var testFile = Path.Combine(folder, $".fotospiel-write-test-{Guid.NewGuid():N}.tmp");
            File.WriteAllText(testFile, "ok");
            File.Delete(testFile);
            return true;
        }
        catch
        {
            return false;
        }
    }

    private string FormatLastSeen()
    {
        if (string.IsNullOrWhiteSpace(_settings.LastSeenAt) || string.IsNullOrWhiteSpace(_settings.LastSeenFile))
        {
            return "Letzte Datei: —";
        }

        if (DateTimeOffset.TryParse(_settings.LastSeenAt, out var timestamp))
        {
            return $"Letzte Datei: {_settings.LastSeenFile} ({timestamp:HH:mm})";
        }

        return $"Letzte Datei: {_settings.LastSeenFile}";
    }

    private string FormatConnectExpiry()
    {
        if (string.IsNullOrWhiteSpace(_settings.ConnectExpiresAt))
        {
            return "Verbindungscode: —";
        }

        if (!DateTimeOffset.TryParse(_settings.ConnectExpiresAt, out var expiry))
        {
            return "Verbindungscode: —";
        }

        var remaining = expiry - DateTimeOffset.Now;
        if (remaining <= TimeSpan.Zero)
        {
            return "Verbindungscode: abgelaufen";
        }

        if (remaining.TotalHours >= 1)
        {
            return $"Verbindungscode: {Math.Ceiling(remaining.TotalHours)} h gueltig";
        }

        return $"Verbindungscode: {Math.Ceiling(remaining.TotalMinutes)} min gueltig";
    }

    private string? ResolveTestUrl()
    {
        var manual = NormalizeUrl(ManualUploadUrlBox.Text);
        if (!string.IsNullOrWhiteSpace(manual))
        {
            return manual;
        }

        if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
        {
            return _settings.UploadUrl;
        }

        var normalizedBase = NormalizeBaseUrl(BaseUrlBox.Text);
        return normalizedBase;
    }

    private static string? NormalizeUrl(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var trimmed = value.Trim();
        if (Uri.TryCreate(trimmed, UriKind.Absolute, out _))
        {
            return trimmed;
        }

        return null;
    }

    private static int ResolveUploadDelay(int selectedIndex)
    {
        return selectedIndex switch
        {
            0 => 0,
            2 => 1500,
            _ => 500,
        };
    }

    private static int ResolveUploadTempoIndex(int delay)
    {
        if (delay <= 0)
        {
            return 0;
        }

        if (delay >= 1500)
        {
            return 2;
        }

        return 1;
    }

    private static string? ResolveResponseFormat(int selectedIndex)
    {
        return selectedIndex switch
        {
            1 => "json",
            2 => "xml",
            _ => null,
        };
    }

    private static int ResolveResponseFormatIndex(string? format)
    {
        if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
        {
            return 1;
        }

        if (string.Equals(format, "xml", StringComparison.OrdinalIgnoreCase))
        {
            return 2;
        }

        return 0;
    }

    private static string NormalizePatternInput(string? input)
    {
        if (string.IsNullOrWhiteSpace(input))
        {
            return string.Empty;
        }

        var parts = SplitPatterns(input);
        return string.Join(';', parts);
    }

    private bool MatchesIncludePatterns(string fileName)
    {
        var patterns = SplitPatterns(_settings.IncludePatterns);
        if (patterns.Count == 0)
        {
            return true;
        }

        return patterns.Any(pattern => IsPatternMatch(fileName, pattern));
    }

    private bool MatchesExcludePatterns(string fileName)
    {
        var patterns = SplitPatterns(_settings.ExcludePatterns);
        if (patterns.Count == 0)
        {
            return false;
        }

        return patterns.Any(pattern => IsPatternMatch(fileName, pattern));
    }

    private static List<string> SplitPatterns(string? input)
    {
        if (string.IsNullOrWhiteSpace(input))
        {
            return new List<string>();
        }

        return input
            .Split(new[] { ';', ',', '\n' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Where(pattern => pattern.Length > 0)
            .ToList();
    }

    private static bool IsPatternMatch(string fileName, string pattern)
    {
        var span = fileName.AsSpan();
        var token = pattern.AsSpan();
        return IsPatternMatch(span, token);
    }

    private static bool IsPatternMatch(ReadOnlySpan<char> text, ReadOnlySpan<char> pattern)
    {
        var textIndex = 0;
        var patternIndex = 0;
        var starIndex = -1;
        var matchIndex = 0;

        while (textIndex < text.Length)
        {
            if (patternIndex < pattern.Length
                && (pattern[patternIndex] == '?' || char.ToLowerInvariant(pattern[patternIndex]) == char.ToLowerInvariant(text[textIndex])))
            {
                textIndex++;
                patternIndex++;
                continue;
            }

            if (patternIndex < pattern.Length && pattern[patternIndex] == '*')
            {
                starIndex = patternIndex;
                matchIndex = textIndex;
                patternIndex++;
                continue;
            }

            if (starIndex != -1)
            {
                patternIndex = starIndex + 1;
                matchIndex++;
                textIndex = matchIndex;
                continue;
            }

            return false;
        }

        while (patternIndex < pattern.Length && pattern[patternIndex] == '*')
        {
            patternIndex++;
        }

        return patternIndex == pattern.Length;
    }

    private void UpdateAdvancedLockState()
    {
        var connected = !string.IsNullOrWhiteSpace(_settings.UploadUrl);
        var unlocked = SettingsUnlockToggle?.IsChecked ?? false;
        var enabled = !connected || unlocked;

        BaseUrlBox.IsEnabled = enabled;
        MaxUploadsBox.IsEnabled = enabled;
        UploadTempoBox.IsEnabled = enabled;
        IncludePatternsBox.IsEnabled = enabled;
        ExcludePatternsBox.IsEnabled = enabled;
        ResponseFormatBox.IsEnabled = enabled;
        ManualUploadUrlBox.IsEnabled = enabled;
        ManualUsernameBox.IsEnabled = enabled;
        ManualPasswordBox.IsEnabled = enabled;
        TestConnectionButton.IsEnabled = enabled;
        SaveAdvancedButton.IsEnabled = enabled;
    }

    private void RefreshProfiles()
    {
        ProfilesBox.ItemsSource = _settings.Profiles.Select(profile => profile.DisplayName).ToList();
    }

    private void LoadProfileButton_Click(object? sender, RoutedEventArgs e)
    {
        var index = ProfilesBox.SelectedIndex;
        if (index < 0 || index >= _settings.Profiles.Count)
        {
            UpdateStatus("Bitte zuerst ein Profil auswaehlen.");
            return;
        }

        var profile = _settings.Profiles[index];
        _settings.BaseUrl = NormalizeBaseUrl(profile.BaseUrl) ?? _settings.BaseUrl;
        _settings.UploadUrl = profile.UploadUrl;
        _settings.Username = profile.Username;
        _settings.Password = profile.Password;
        _settings.ResponseFormat = profile.ResponseFormat;
        _settings.WatchFolder = profile.WatchFolder;
        _settings.IncludePatterns = profile.IncludePatterns;
        _settings.ExcludePatterns = profile.ExcludePatterns;
        _settings.MaxConcurrentUploads = profile.MaxConcurrentUploads > 0 ? profile.MaxConcurrentUploads : _settings.MaxConcurrentUploads;
        _settings.UploadDelayMs = profile.UploadDelayMs;
        _settingsStore.Save(_settings);

        ApplySettings();
        UpdateStatus("Profil geladen.");
    }

    private void SaveProfileButton_Click(object? sender, RoutedEventArgs e)
    {
        var label = _settings.EventName ?? "Neues Profil";
        var profile = new PhotoboothProfile
        {
            Label = label,
            EventName = _settings.EventName,
            BaseUrl = _settings.BaseUrl,
            UploadUrl = _settings.UploadUrl,
            Username = _settings.Username,
            Password = _settings.Password,
            ResponseFormat = _settings.ResponseFormat,
            WatchFolder = _settings.WatchFolder,
            IncludePatterns = _settings.IncludePatterns,
            ExcludePatterns = _settings.ExcludePatterns,
            MaxConcurrentUploads = _settings.MaxConcurrentUploads,
            UploadDelayMs = _settings.UploadDelayMs,
        };

        _settings.Profiles.RemoveAll(existing => string.Equals(existing.DisplayName, profile.DisplayName, StringComparison.OrdinalIgnoreCase));
        _settings.Profiles.Insert(0, profile);
        _settingsStore.Save(_settings);
        RefreshProfiles();
        ProfilesBox.SelectedIndex = 0;
        UpdateStatus("Profil gespeichert.");
    }

    private async void LogCopyButton_Click(object? sender, RoutedEventArgs e)
    {
        var content = ReadLogForCopy();
        if (string.IsNullOrWhiteSpace(content))
        {
            UpdateStatus("Log ist leer.");
            return;
        }

        var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
        if (clipboard is null)
        {
            UpdateStatus("Zwischenablage nicht verfuegbar.");
            return;
        }

        await clipboard.SetTextAsync(content);
        UpdateStatus("Log kopiert.");
    }

    private void AppendLog(string message)
    {
        var line = $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss} {message}";
        _logBuffer.Add(line);
        while (_logBuffer.Count > 200)
        {
            _logBuffer.RemoveAt(0);
        }

        try
        {
            File.AppendAllLines(_settingsStore.LogPath, new[] { line });
        }
        catch
        {
            // ignore file errors
        }
    }

    private string ReadLogForCopy()
    {
        try
        {
            if (File.Exists(_settingsStore.LogPath))
            {
                var lines = File.ReadAllLines(_settingsStore.LogPath);
                return string.Join(Environment.NewLine, lines.TakeLast(200));
            }
        }
        catch
        {
            // ignore
        }

        return _logBuffer.Count > 0 ? string.Join(Environment.NewLine, _logBuffer) : string.Empty;
    }

    private void UpdatePresetButtons()
    {
        DslrBoothPresetButton.IsVisible = OperatingSystem.IsWindows();
        SparkboothPresetButton.IsVisible = OperatingSystem.IsWindows() || OperatingSystem.IsMacOS();
    }

    private void ApplyPresetFolder(string? folder)
    {
        if (string.IsNullOrWhiteSpace(folder))
        {
            UpdateStatus("Preset-Ordner nicht verfügbar.");
            return;
        }

        try
        {
            Directory.CreateDirectory(folder);
        }
        catch
        {
            UpdateStatus("Preset-Ordner konnte nicht erstellt werden.");
            return;
        }

        _settings.WatchFolder = folder;
        _settingsStore.Save(_settings);
        FolderText.Text = folder;
        UpdateFolderHealth();
        StartUploadPipelineIfReady();
    }

    private static string? GetDslrBoothFolder()
    {
        return OperatingSystem.IsWindows() ? @"C:\dslrBooth" : null;
    }

    private static string? GetSparkboothFolder()
    {
        var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        if (string.IsNullOrWhiteSpace(documents))
        {
            return null;
        }

        return Path.Combine(documents, "sparkbooth");
    }
}
