using Avalonia.Controls.ApplicationLifetimes;
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using VeloeMinecraftLauncher.Entity.LauncherProfiles;
using VeloeMinecraftLauncher.Utils;
using Microsoft.AspNetCore.SignalR.Client;
using VeloeMinecraftLauncher.Entity.McStatus;
using System.Timers;
using System.Reflection;
using Serilog;
using Avalonia.Controls;
using Avalonia.Threading;
using System.IO.Compression;
using ReactiveUI.Validation.Extensions;
using System.Collections.Generic;
using VeloeMinecraftLauncher.Models.Entity;
using VeloeMinecraftLauncher.Models;
using System.Linq;
using System.Threading;
using System.ComponentModel;
using VeloeMinecraftLauncher.Utils.Logger;
using VeloeMinecraftLauncher.Utils.Downloader;
using VeloeMinecraftLauncher.Utils.Starter;
using System.Windows.Input;

namespace VeloeMinecraftLauncher.ViewModels;

public class MainWindowViewModel : ViewModelBase
{
    public MainWindowViewModel()
    {
        _downloadedVersions = new();
        _serverPanels = new();

        try
        {
            //creating logger
            EventSink eventSink = new(null);
            eventSink.DataReceived += LogHandler;
            var hook = new CaptureFilePathHook();
            _logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Sink(eventSink, SettingsService.Instance.ConsoleLogEventLevel)
                .WriteTo.File("launcher.log", SettingsService.Instance.FileLogEventLevel, fileSizeLimitBytes: SettingsService.Instance.MaxLog * 1024, rollOnFileSizeLimit: true, hooks: hook)// restricted... is Optional
                .CreateLogger();
            SettingsService.logger = _logger;
            SettingsService.logFilePath = hook;
            //loading settings
            _logger.Debug("Loading settings.");
            //SettingsService.LoadSettings();
            _username = SettingsService.Instance.Username;

            //loading local verions
            _logger.Debug("Loading local versions.");
            ScanForDownloadedVersions();
        }
        catch (Exception ex)
        {
            _logger ??= new LoggerConfiguration().MinimumLevel.Debug().CreateLogger();
            OpenErrorWindow(ex);
        }
        finally
        {
            this.ValidationRule(
                viewModel => viewModel.Username,
                value => { return !string.IsNullOrEmpty(value); },
                "Empty username.");
        }
        UpdateVersions = ReactiveCommand.Create(ScanForDownloadedVersions);
        SettingsTabViewModel = new SettingsViewModel(_logger,UpdateVersions);
        VersionsDownloaderTabViewModel = new VersionsDownloaderViewModel(_logger,UpdateVersions);

        Task.Run(async () =>
        {
            UpdateUpdater();

            try
            {
                //check launcher update
                _logger.Information("Checking launcher versions updates.");
                _latestLauncherInfo = await Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
                if (_latestLauncherInfo is not null && _latestLauncherInfo.Latest is not null)
                {
                    _logger.Information("Launcher version on server: {0}", _latestLauncherInfo.Latest);
                    _logger.Information("Launcher version: {0}", Assembly.GetExecutingAssembly().GetName().Version);

                    if (new Version(_latestLauncherInfo.Latest) > Assembly.GetExecutingAssembly().GetName().Version)
                    {
                        _logger.Debug("Update available!");
                        IsUpdateAvailable = true;
                    }
                }
                else
                    _logger.Warning("Can't get latest verion info! Skipping...");
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
            
            try
            {
                Changelogs = new ObservableCollection<Changelog>(await Downloader.DownloadAndDeserializeJsonData<List<Changelog>>("https://files.veloe.link/launcher/changelog.json") ?? new List<Changelog>());
                this.RaisePropertyChanged(nameof(Changelogs));
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
            try
            {

                _logger.Debug("Connecting to WebSoket");
                //conection to my servers 

                var serverInfo = await Downloader.DownloadAndDeserializeJsonData<Dictionary<string, string[]>>("https://files.veloe.link/launcher/serversInfo.json");

                if (serverInfo is not null)
                {
                    _connection = new HubConnectionBuilder()
                    .WithUrl("https://monitor.veloe.link/hubs/data")
                    .WithAutomaticReconnect()
                    .Build();

                    Func<Exception, Task> reconnecting = ex => Task.Run(() =>
                    {
                        _logger.Warning("Reconnecting to WebCoket...");
                    });
                    Func<string, Task> reconnected = str => Task.Run(() =>
                    {
                        _logger.Warning("Reconnected to WebCoket.");
                        foreach (var server in serverInfo)
                        {
                            _connection.InvokeAsync("ConnectToGroup", server.Key);
                        }
                    });

                    _connection.Reconnecting += reconnecting;
                    _connection.Reconnected += reconnected;
                    
                    await Dispatcher.UIThread.InvokeAsync(()=>
                    {
                        foreach (var server in serverInfo)
                            ServerPanels.Add(CreateServerPanel(server.Key, server.Value[0], server.Value[1]));
                    });

                    await _connection.StartAsync();

                    foreach (var server in serverInfo)
                    {
                        await _connection.InvokeAsync("ConnectToGroup", server.Key);
                    }
                    _logger.Debug("Connected to WebSoket");
                }

                _consoleOutputTimer.Elapsed += UpdateConsoleOutput;
                _consoleOutputTimer.AutoReset = false;
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
            try
            {
                _logger.Debug("Checking modpacks updates...");
                var modpacksInfo = await Downloader.DownloadAndDeserializeJsonData<List<Modpack>>("https://files.veloe.link/launcher/modpacks.json") ?? new List<Modpack>();
                var installedModpacks = //DownloadedVersions.Where(v => modpacksInfo.Select(m => m.Name).Contains(v.version)).Join(modpacksInfo, (v, m) => v.version == m.);
                    from downloadedVersion in DownloadedVersions
                    join modpack in modpacksInfo on downloadedVersion.Version equals modpack.Name
                    select new
                    {
                        Name = downloadedVersion.Version,
                        downloadedVersion.Path,
                        modpack.Revision
                    };
                var modpacksToUpdate = new List<string>();
                var modpacksRevsionUnknown = new List<string>();

                foreach (var modpack in installedModpacks)
                {
                    if (File.Exists(Path.GetDirectoryName(modpack.Path) + "/revision.json"))
                    {
                        try
                        {
                            if (modpack.Revision > JsonSerializer.Deserialize<int>(File.ReadAllText(Path.GetDirectoryName(modpack.Path) + "/revision.json")))
                                modpacksToUpdate.Add(modpack.Name);
                        }
                        catch (Exception)
                        {
                            modpacksRevsionUnknown.Add(modpack.Name);
                        }
                    }
                    else
                        modpacksRevsionUnknown.Add(modpack.Name);
                }
                var message = string.Empty;
                if (modpacksToUpdate.Any())
                    message = $"Found updates for selected installed modpacks:{Environment.NewLine}    {string.Join($"{Environment.NewLine}    ", modpacksToUpdate)}";

                if (modpacksRevsionUnknown.Any())
                {
                    if (!string.IsNullOrEmpty(message))
                        message += Environment.NewLine;
                    message += $"Can't get revision for selected installed modpacks:{Environment.NewLine}    {string.Join($"{Environment.NewLine}    ", modpacksRevsionUnknown)}{Environment.NewLine}Please update them for correct verion checking!";
                }

                if (!string.IsNullOrEmpty(message))
                    OpenErrorWindow(message);

                _logger.Debug("Checking modpacks updates finished successfully!");
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
        });
        
    }

    public MainWindowViewModel(Action hide, Action show) : this()
    {
        _hide = hide;
        _show = show;
    }

    System.Timers.Timer _consoleOutputTimer = new(250);

    private HubConnection? _connection;
    private string _downloadButton = "Download versions";
    private string _startButton = "Start Minecraft";
    private string _username = string.Empty;
    private StringBuilder _consoleText = new();
    private int _downloadedIndex;
    private bool _isNoGameRunning = true;
    private bool _isUpdateAvailable = false;
    private string _settingsButton = "Settings";
    private string _startButtonOutput = string.Empty;
    private CancellationTokenSource _tokenSource = new();

    private Action _hide = () => { };
    private Action _show = () => { };

    private ICommand UpdateVersions;

    LatestLauncherVersion? _latestLauncherInfo;
    DownloadedVersion? _downloadedVersion;
    DownloadedVersion? _startedVersion;

    ObservableCollection<DownloadedVersion> _downloadedVersions;
    ObservableCollection<ServerPanelModel> _serverPanels;

    public ObservableCollection<DownloadedVersion> DownloadedVersions 
    {
        get => _downloadedVersions;
        set => this.RaiseAndSetIfChanged(ref _downloadedVersions, value);
    }

    public ObservableCollection<ServerPanelModel> ServerPanels
    {
        get => _serverPanels;
        set => this.RaiseAndSetIfChanged(ref _serverPanels, value);
    }

    public ObservableCollection<Changelog>? Changelogs { get; private set; }

    public DownloadedVersion? DownloadedVersion
    {
        get => _downloadedVersion;
        set => this.RaiseAndSetIfChanged(ref _downloadedVersion, value);
    }

    public bool DownloadedVersionNotNull => DownloadedVersion is not null;

    public int DownloadedIndex
    {
        get => _downloadedIndex;
        set
        {
            this.RaiseAndSetIfChanged(ref _downloadedIndex, value);
            if (value >= 0 && value < DownloadedVersions.Count)
                DownloadedVersion = DownloadedVersions[value];
        }
    }
    public string DownloadButton {
        get => _downloadButton;
        set => this.RaiseAndSetIfChanged(ref _downloadButton, value);
    }

    public string StartButton
    {
        get => _startButton;
        set => this.RaiseAndSetIfChanged(ref _startButton, value);
    }

    public string Username 
    { 
        get => _username;
        set
        {
            this.RaiseAndSetIfChanged(ref _username, value);
            this.RaisePropertyChanged(nameof(IsStartButtonEnabled));
        }
    }

    public string ConsoleText
    {
        get 
        {
            if (_consoleText.Length < UInt16.MaxValue)
                return _consoleText.ToString();
            else
                return _consoleText.ToString(_consoleText.Length - UInt16.MaxValue, UInt16.MaxValue);
        }
        set
        {
            _consoleText.Clear();
            _consoleText.Append(value);
            this.RaisePropertyChanged(nameof(ConsoleText));
            ConsoleTextCaretIndex = int.MaxValue;
        }
    }

    public string StartButtonOutput
    {
        get => _startButtonOutput;
        set => this.RaiseAndSetIfChanged(ref _startButtonOutput, value);
    }

    public string SettingsButton
    {
        get => _settingsButton;
        set => this.RaiseAndSetIfChanged(ref _settingsButton, value);
    }

    public bool IsNoGameRunning
    {
        get => _isNoGameRunning;
        set
        {
            this.RaiseAndSetIfChanged(ref _isNoGameRunning, value);
            this.RaisePropertyChanged(nameof(IsStartButtonEnabled));
        }
    }

    public bool IsStartButtonEnabled
    {
        get => IsNoGameRunning && !string.IsNullOrEmpty(Username);
    }

    public bool IsUpdateAvailable
    {
        get => _isUpdateAvailable;
        set => this.RaiseAndSetIfChanged(ref _isUpdateAvailable, value);
    }

    public int ConsoleTextCaretIndex
    {
        get => int.MaxValue;
        set => this.RaisePropertyChanged(nameof(ConsoleTextCaretIndex));
    }

    public SettingsViewModel SettingsTabViewModel { get; set; }
    public VersionsDownloaderViewModel VersionsDownloaderTabViewModel { get; set; }

    public void StartMinecraft()
    {
        StartMinecraft(string.Empty);
    }

    public async void StartMinecraft(string additionalArgs)
    {
        if (!IsNoGameRunning) return;

        await Task.Run(async() =>
        {
            try
            {
                _logger.Debug("Starting minecraft.");
                if (DownloadedVersion is null)
                {
                    IsNoGameRunning = true;
                    return;
                }

                IsNoGameRunning = false;

                int version = 0;
                var arguments = string.Empty;

                using (StreamReader reader = new(DownloadedVersion.Path))
                {
                    var json = reader.ReadToEnd();

                    var versionJson = JsonSerializer.Deserialize<Entity.Version.Version>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

                    if (versionJson is null)
                        throw new ArgumentNullException(nameof(versionJson));

                    if (SettingsService.Instance.CheckGameAssets)
                    {
                        TaskStatus result;

                        var parameters = new DownloaderParameters();
                        parameters.Action.DownloadingFileName = value => StartButtonOutput = value;
                        parameters.Action.IsControlsEnabled = value => IsNoGameRunning = false;

                        if (string.IsNullOrEmpty(versionJson.InheritsFrom))
                        {
                            parameters.Data.VersionJson = versionJson;
                            await new Downloader(parameters,_logger).DownloadClient(_tokenSource.Token);
                            result = TaskStatus.RanToCompletion;
                        }
                        else
                        {
                            var downloader = new Downloader(parameters,_logger);
                            if (versionJson.Libraries.Any(x => x.Name is not null && x.Name.Contains("fabric")))
                            {
                                parameters.Data.VersionJson = versionJson;
                                await downloader.DownloadFabricLibraries(_tokenSource.Token);
                            }

                            using StreamReader inheritsFromReader = new(SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.InheritsFrom + "/" + versionJson.InheritsFrom + ".json");
                            string jsonInheritsFrom = inheritsFromReader.ReadToEnd();
                            var inheritsFromJson = JsonSerializer.Deserialize<Entity.Version.Version>(jsonInheritsFrom, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

                            if (inheritsFromJson is not null)
                            {
                                parameters.Data.VersionJson = inheritsFromJson;
                                await downloader.DownloadClient(_tokenSource.Token);
                                result = TaskStatus.RanToCompletion;
                            }
                            else
                                result = TaskStatus.Faulted;
                        }

                        if (result != TaskStatus.RanToCompletion)
                        {
                            IsNoGameRunning = true;
                            StartButtonOutput = "Checking game files task faulted.";
                            return;
                        }
                    }

                    arguments = StartCommandBuilder.Build(versionJson, Username);

                    if (!SettingsService.Instance.UseCustomJava)
                    {
                        if (versionJson?.JavaVersion?.MajorVersion is not null)
                        {
                            _logger.Debug("Java version required: {0}", versionJson.JavaVersion.MajorVersion);
                            version = versionJson.JavaVersion.MajorVersion.Value;
                        }
                        else
                        {
                            if (!string.IsNullOrEmpty(versionJson?.InheritsFrom))
                            {
                                using StreamReader inheritsFromReader = new(SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.InheritsFrom + "/" + versionJson.InheritsFrom + ".json");
                                string jsonInheritsFrom = inheritsFromReader.ReadToEnd();
                                var inheritsFromJson = JsonSerializer.Deserialize<Entity.Version.Version>(jsonInheritsFrom, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                                if (inheritsFromJson?.JavaVersion?.MajorVersion is not null)
                                {
                                    _logger.Debug("Java version required: {0}", inheritsFromJson.JavaVersion.MajorVersion);
                                    version = inheritsFromJson.JavaVersion.MajorVersion.Value;
                                }
                            }
                            else
                            {
                                version = 8;
                            }
                        }
                    }
                }

                if (DownloadedVersion is null)
                    return;

                string javaPath = SettingsService.Instance.JavaPath;

                if (!SettingsService.Instance.UseCustomJava)
                {
                    javaPath = Path.GetFullPath(SettingsService.Instance.MinecraftForlderPath + "javaruntime/" + version + "/bin/java");
                    if (OperatingSystem.IsWindows())
                        javaPath += ".exe";
                }
                else
                {
                    javaPath = Path.Combine(SettingsService.Instance.JavaPath, "bin/java");
                    if (OperatingSystem.IsWindows())
                        javaPath += ".exe";
                }

                _logger.Debug("Java version path: {0}", Path.Combine(SettingsService.Instance.MinecraftForlderPath, javaPath));
                _logger.Debug("Minecraft arguments: {0}", arguments);

                ProcessStartInfo proc;
                proc = new ProcessStartInfo
                {
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true,
                    FileName = Path.GetFullPath(Path.Combine(SettingsService.Instance.MinecraftForlderPath, javaPath)),
                    StandardErrorEncoding = Encoding.UTF8,
                    WorkingDirectory = Path.GetDirectoryName(Path.Combine(SettingsService.Instance.MinecraftForlderPath, javaPath)),
                    Arguments = arguments + additionalArgs
                };

                Process minecraft = new()
                {
                    StartInfo = proc
                };

                if (!SettingsService.Instance.UseCustomJava)
                    minecraft.StartInfo.EnvironmentVariables["JAVA_HOME"] = $"{SettingsService.Instance.MinecraftForlderPath}javaruntime/{version}";
                else
                    minecraft.StartInfo.EnvironmentVariables["JAVA_HOME"] = SettingsService.Instance.JavaPath;
                minecraft.StartInfo.EnvironmentVariables["PATH"] = "%JAVA_HOME%\bin;%PATH%";
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                Task.Run(() =>
                {
                    try
                    {
                        _logger.Debug("Starting java process.");

                        minecraft.OutputDataReceived += OutputHandler;
                        minecraft.ErrorDataReceived += OutputHandler;

                        //* Start process and handlers            
                        //minecraft.WaitForExit();

                        minecraft.EnableRaisingEvents = true;

                        IsNoGameRunning = false;
                        _startedVersion = DownloadedVersion;
                        minecraft.Start();

                        if (SettingsService.Instance.HideLauncher)
                            _hide();
                        minecraft.Exited += ProcessExited;

                        minecraft.BeginOutputReadLine();
                        minecraft.BeginErrorReadLine();

                        if (!SettingsService.Instance.GameLogToLauncher)
                        {
                            minecraft.OutputDataReceived -= OutputHandler;
                            minecraft.CancelOutputRead();
                        }

                        return Task.CompletedTask;
                    }
                    catch (Exception ex)
                    {
                        IsNoGameRunning = true;
                        OpenErrorWindow(ex);
                        return Task.CompletedTask;
                    }
                });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                _logger.Debug("Updating Settings");
                SettingsService.Instance.Username = _username;
                SettingsService.Instance.LastChosenVersion = DownloadedVersion.Version;
                SettingsService.SaveSettings();

            }
            catch (Exception ex)
            {
                IsNoGameRunning = true;
                OpenErrorWindow(ex);
            }
        });
    }

    void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
    {
        //Todo create multiple TextBlocks if char limit
        if (!_consoleOutputTimer.Enabled)
        {
            _consoleOutputTimer.Stop();
            _consoleOutputTimer.Start();
        }
        _consoleText.Append(outLine.Data + "\n");          
    }

    void LogHandler(object? sendingProcess, EventArgs args)
    {
        if (!_consoleOutputTimer.Enabled)
        {
            _consoleOutputTimer.Stop();
            _consoleOutputTimer.Start();
        }
        _consoleText.Append(((MyEventArgs)args).Data);
    }

    void UpdateConsoleOutput(object? source, ElapsedEventArgs e)
    {
        this.RaisePropertyChanged(nameof(ConsoleText));

        ScrollToEnd("ConsoleScroll");
    }

    void ScrollToEnd(string scrollName)
    {
        if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            Dispatcher.UIThread.InvokeAsync(() =>
            {
                var scroll = desktop.MainWindow?.GetControl<ScrollViewer>(scrollName);
                scroll?.ScrollToEnd();
            });

        }
    }

    void ProcessExited(object? sendingProcess, EventArgs e)
    {
        if(sendingProcess is Process minecraftProcess)
        {
            minecraftProcess.Exited -= ProcessExited;
            minecraftProcess.OutputDataReceived -= OutputHandler;
            minecraftProcess.ErrorDataReceived -= OutputHandler;
            minecraftProcess.CancelOutputRead();
            minecraftProcess.CancelErrorRead();

            _logger.Debug("Exit code: {0}", minecraftProcess.ExitCode);

            StartButtonOutput = "";
            IsNoGameRunning = true;
            if (SettingsService.Instance.HideLauncher)
                _show();
            if (minecraftProcess.ExitCode is not (0 or 2))
                OpenErrorWindow(new JavaProcessException($"Minecraft process exited with an error code {minecraftProcess.ExitCode}.\nCheck log in game folder or launcher console.", $"{SettingsService.Instance.MinecraftForlderPath}versions/{_startedVersion?.Version ?? "NULL"}/crash-reports"));
            else if (minecraftProcess.ExitCode is 2)
            {
                OpenErrorWindow(new Exception("JVM exited on the startup (Exit code 2). Check your Java installation. Get more info in the laucher console."));
            }
            minecraftProcess.Dispose();
        }
    }

    public void ScanForDownloadedVersions()
    {
        DownloadedVersions ??= new();

        DownloadedVersions.Clear();
        DirectoryInfo versions = new( Path.Combine(SettingsService.Instance.MinecraftForlderPath,"versions"));
        try
        {
            var dirs = versions.GetDirectories("*", SearchOption.TopDirectoryOnly);
            LauncherProfiles profiles = new()
            {
                SelectedProfile = "NotImplemented"
            };

            foreach (var dir in dirs)
            {
                _logger.Debug("Checking folder {0}", dir.Name);
                string checkedPath;
                if (File.Exists(checkedPath = dir.FullName + "/" + dir.Name + ".json"))
                {
                    _logger.Debug("Found version {0}",dir.Name);
                    DownloadedVersions.Add(new DownloadedVersion(checkedPath,dir.Name));
                    profiles.Profiles.Add($"Version {dir.Name}", new Profile() { Name = dir.Name, LastVersionId = dir.Name, LauncherVisibilityOnGameClose = "keep the launcher open" });
                }

            }

            if (SettingsService.Instance.LastChosenVersion != null)
            {
                DownloadedIndex = 0;
                for (int i = 0; i < DownloadedVersions.Count; i++)
                {
                    if (DownloadedVersions[i].Version == SettingsService.Instance.LastChosenVersion)
                    {
                        DownloadedIndex = i;
                        break;
                    }
                }
                
            }
            if (!File.Exists(SettingsService.Instance.MinecraftForlderPath + "launcher_profiles.json"))
            {
                var file = File.Create(SettingsService.Instance.MinecraftForlderPath + "launcher_profiles.json");
                file.Close();
                file.Dispose();
            }
            var lpString = JsonSerializer.Serialize(profiles, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            File.WriteAllText(SettingsService.Instance.MinecraftForlderPath + "launcher_profiles.json", lpString);
        }
        catch (DirectoryNotFoundException)
        {
            Directory.CreateDirectory(SettingsService.Instance.MinecraftForlderPath + "versions");
            return;
        }
        
    }

    public void DownloadUpdate()
    {
        _logger.Debug("Started updater.exe");
        Process updater = new();
        updater.StartInfo.FileName = "Updater";
        if (OperatingSystem.IsWindows())
            updater.StartInfo.FileName += ".exe";

        if (!File.Exists(updater.StartInfo.FileName))
            Task.Run(UpdateUpdater).Wait();

        if (File.Exists(updater.StartInfo.FileName))
        {
            updater.Start();

            if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is not null)
                desktop.MainWindow.Close();
        }
        else
            OpenErrorWindow(new FileNotFoundException($"File {updater.StartInfo.FileName} not found!"));
    }

    private async void UpdateUpdater()
    {
        try
        {
            string fileName = "Updater";
            if (OperatingSystem.IsWindows())
                fileName += ".exe";
            if (File.Exists(fileName))
            {
                var latestLauncherVersion = await Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
                
                if (OperatingSystem.IsWindows())
                {
                    FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
                    if (latestLauncherVersion?.Updater is not null && fileVersionInfo?.FileVersion is not null)
                    {
                        _logger.Information("Latest updater version on server: {0}", latestLauncherVersion.Updater);
                        _logger.Information("Updater version: {0}", fileVersionInfo.FileVersion);

                        if (!(new Version(latestLauncherVersion.Updater) > new Version(fileVersionInfo.FileVersion)))
                        {
                            _logger.Information($"No update for \"{fileName}\" required.");
                            return;
                        }
                    }
                    else
                        _logger.Warning("Version verification for {0} failed: latestLauncherVersion or fileVersionInfo does not contain version info or null!", fileName);
                }
                
                if (OperatingSystem.IsLinux())
                {
                    using var updaterProcess = Process.Start(new ProcessStartInfo() { FileName = fileName, Arguments = " --version", RedirectStandardOutput = true });
                    updaterProcess?.WaitForExit();

                    var versionStr = System.Text.RegularExpressions.Regex.Match(updaterProcess?.StandardOutput.ReadToEnd() ?? string.Empty, "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+").Value;
                    if (!string.IsNullOrWhiteSpace(versionStr) && 
                        latestLauncherVersion?.Updater is not null && 
                        !(new Version(latestLauncherVersion.Updater) > new Version(versionStr)))
                    {
                        _logger.Information($"No update for \"{fileName}\" required.");
                        return;
                    }
                }
            }
                           
            try
            {
                _logger.Information("Downloading updater.zip");
                string url = String.Empty;

                if (OperatingSystem.IsWindows())
                    url = "https://files.veloe.link/launcher/update/updater.zip";

                if (OperatingSystem.IsLinux())
                    url = "https://files.veloe.link/launcher/update/updater_linux-x64.zip";

                if (url == String.Empty)
                    return;

                await Downloader.DownloadFileAsync(url, "updater.zip", _tokenSource.Token);
            }
            catch (Exception)
            {
                _logger.Error("Error occured on getting updater.zip from the server.");
                throw;
            }
            _logger.Information("Unpacking updater.zip");
            ZipFile.ExtractToDirectory($"updater.zip", Directory.GetCurrentDirectory(), true);

            _logger.Information("Deleting updater.zip");
            File.Delete($"updater.zip");
            
        }
        catch (Exception ex)
        {
            OpenErrorWindow(ex);
        }

    }

    private ServerPanelModel CreateServerPanel(string name, string address, string clientName)
    {
        ServerPanelModel serverPanelModel = new(name, "Wait for update...", "No players.","-/-", address, clientName);

        if (SettingsService.Instance.ServerAutoConnectLinks.ContainsKey(name))
            serverPanelModel.UserClientName = SettingsService.Instance.ServerAutoConnectLinks[name];

        System.Timers.Timer timeoutTimer = new System.Timers.Timer(30000);

        timeoutTimer.Elapsed += (object? source, ElapsedEventArgs e) => 
        { 
            serverPanelModel.Status = $"{serverPanelModel.Name}: Offline"; 
            serverPanelModel.Players = "-/-";
            serverPanelModel.Tip = "Server timeout.";
        };
        timeoutTimer.Start();

        _connection?.On<string>($"Update{serverPanelModel.Name}", (message) =>
        {
            McStatus? status = JsonSerializer.Deserialize<McStatus>(message, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

            if(status is not null)
            {
                serverPanelModel.Status = $"{serverPanelModel.Name}: Online";
                serverPanelModel.Players = $"{status.NumPlayers}/{status.MaxPlayers}";
                serverPanelModel.Tip = String.Empty;
                if (UInt16.Parse(status.NumPlayers ?? "0") > 0)
                    serverPanelModel.Tip = string.Join('\n',status.Players);
                else
                    serverPanelModel.Tip = "No players.";
            }      
            timeoutTimer.Stop();
            timeoutTimer.Start();
        });

        return serverPanelModel;
    }

    public void OnServerPanelClick(ServerPanelModel server)
    {
        if (string.IsNullOrWhiteSpace(server?.Name))
            return;

        if (SettingsService.Instance.ServerAutoConnectLinks.ContainsKey(server.Name))
        {
            var downloadedVersion = DownloadedVersions.FirstOrDefault(x => string.Equals(x.Version, SettingsService.Instance.ServerAutoConnectLinks[server.Name], StringComparison.Ordinal));

            if (downloadedVersion != null)
            {
                DownloadedIndex = DownloadedVersions.IndexOf(downloadedVersion);
                StartMinecraft($" --server {server.Address}");
                return;
            }
        }

        if (!string.IsNullOrWhiteSpace(server.ClientName))
        {
            var downloadedVersion = DownloadedVersions.FirstOrDefault(x => string.Equals(x.Version, server.ClientName, StringComparison.Ordinal));

            if (downloadedVersion != null)
            {
                DownloadedIndex = DownloadedVersions.IndexOf(downloadedVersion);
                StartMinecraft($" --server {server.Address}");
                return;
            }
        }

        //version not found
        Debug.WriteLine("Version not found, or not set on server!");
    }

    public void SetNewPreferredVersion(ServerPanelModel server)
    {
        if (!SettingsService.Instance.ServerAutoConnectLinks.ContainsKey(server.Name))
            SettingsService.Instance.ServerAutoConnectLinks.Add(server.Name, DownloadedVersion?.Version);
        else
            SettingsService.Instance.ServerAutoConnectLinks[server.Name] = DownloadedVersion?.Version;
        server.UserClientName = DownloadedVersion?.Version;
        SettingsService.SaveSettings();
    }

    public void ResetPreferredVersion(ServerPanelModel server)
    {
        if (SettingsService.Instance.ServerAutoConnectLinks.ContainsKey(server.Name))
        {
            SettingsService.Instance.ServerAutoConnectLinks.Remove(server.Name);
            server.UserClientName = string.Empty;
            SettingsService.SaveSettings();
        }
    }

    public void OnClosing(object sender, CancelEventArgs args)
    {
        VersionsDownloaderTabViewModel?.OnClosing(sender, args);
        _tokenSource.Cancel();
        _tokenSource.Dispose();
    }
}
