using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Handlers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using VeloeMinecraftLauncher.Entity.Assets;
using VeloeMinecraftLauncher.Utils.Starter;

namespace VeloeMinecraftLauncher.Utils.Downloader;

internal class Downloader
{
    static HttpClient _sHttpClient = new();

    public DownloaderParameters Parameters { get; private set; }
    private readonly Serilog.ILogger _logger;

    public Downloader(DownloaderParameters parameters, Serilog.ILogger logger)
    {
        Parameters = parameters;
        _logger = logger;
    }

    public async Task DownloadClient(
        CancellationToken cancellationToken = default)
    {
        try
        {
            //task 1 - download java
            //task 2 - download jar
            //task 3 - download libs
            //task 4 - download assets
            //task 5 - download addons
            //task 6 - download modpacks
            Parameters.Action.IsControlsEnabled(false);
            Parameters.Action.SetProgress(0);
            var tasksCount = 3;
            var tasksIterator = 0;

            if (Parameters.Parameter.DownloadJava) tasksCount++;
            if (Parameters.Parameter.InstallForge || Parameters.Parameter.InstallOptifine || Parameters.Parameter.InstallForgeOptifine || Parameters.Parameter.InstallFabric) tasksCount++;
            if (Parameters.Data.SelectedModpack is not null) tasksCount++;

            Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");

            var handler = new HttpClientHandler() { AllowAutoRedirect = true };

            var ph = new ProgressMessageHandler(handler);
            ph.HttpSendProgress += (_, args) =>
            {
                Parameters.Action.SetProgress(args.ProgressPercentage);
            };

            ph.HttpReceiveProgress += (_, args) =>
            {
                Parameters.Action.SetProgress(args.ProgressPercentage);
            };

            using var httpClient = new HttpClient(ph);

            //downloading json
            Entity.Version.Version? versionJson = Parameters.Data.VersionJson ?? await GetVersionJson();

            ArgumentNullException.ThrowIfNull(versionJson);

            //SetMaxProgressBarValue(size);
            _logger.Debug("Downloading minecraft {0}", versionJson.Id);

            //download java
            if (Parameters.Parameter.DownloadJava)
            {
                try
                {
                    await DownloadJava(httpClient, versionJson, cancellationToken);
                    tasksIterator++;
                    Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
                }
                catch (Exception ex) when (ex is ArgumentException or HttpRequestException)
                {
                    _logger.Warning(ex.Message);
                }
            }

            await DownloadMainJar(httpClient, versionJson, cancellationToken);

            tasksIterator++;
            Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            //end download jar

            //download libraries
            await DownloadLibraries(httpClient, versionJson, cancellationToken);

            tasksIterator++;
            Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");

            await DownloadAssets(versionJson, cancellationToken);

            Parameters.Action.SetProgress(100);
            tasksIterator++;
            Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");

            if (Parameters.Parameter.InstallForge)
            {
                await DownloadForge(httpClient, versionJson, cancellationToken);
            }

            if (Parameters.Parameter.InstallOptifine)
            {
                await DownloadOptifine(httpClient, versionJson, cancellationToken);
            }

            if (Parameters.Parameter.InstallForgeOptifine)
            {
                await DownloadForgeOptifine(httpClient, versionJson, cancellationToken);
            }

            if (Parameters.Parameter.InstallForge || Parameters.Parameter.InstallOptifine || Parameters.Parameter.InstallForgeOptifine || Parameters.Parameter.InstallFabric)
            {
                tasksIterator++;
                Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            }

            if (Parameters.Data.SelectedModpack is not null)
            {
                await DownloadModpack(httpClient, cancellationToken);
                tasksIterator++;
                Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            }

            //RemoveProgressBar(webClient);
            //Progress = 100;
            _logger.Debug("Downloading finished.");
            Parameters.Action.DownloadingFileName("Finished!");
            Parameters.Action.IsControlsEnabled(true);

            //webClient.Dispose();
        }
        catch (TaskCanceledException ex)
        {
            Parameters.Action.IsControlsEnabled(true);
            if (Parameters.Data.SelectedModpack is not null)
                Parameters.Action.DownloadingFileName($"Downloading {Parameters.Data.SelectedModpack.Name} canceled.");
            else if (Parameters.Data.SelectedVersion is not null)
                Parameters.Action.DownloadingFileName($"Downloading {Parameters.Data.SelectedVersion.Id} canceled.");
            else
                Parameters.Action.DownloadingFileName("Downloading canceled: version config is null!");
            _logger.Warning(ex.Message);
            return;// Task.FromCanceled(cancellationToken);
        }
        catch (Exception ex)
        {
            Parameters.Action.IsControlsEnabled(true);
            if (Parameters.Data.SelectedModpack is not null)
                Parameters.Action.DownloadingFileName($"Error occured while downloading {Parameters.Data.SelectedModpack.Name}.");
            else if (Parameters.Data.SelectedVersion is not null)
                Parameters.Action.DownloadingFileName($"Error occured while downloading {Parameters.Data.SelectedVersion.Id}.");
            else
                Parameters.Action.DownloadingFileName($"Error occured while downloading: version config is null!");
            _logger.Error(ex.Message);
            if (ex.StackTrace is not null)
                _logger.Error(ex.StackTrace);
            return;// Task.FromException(ex);
        }
        return;
    }

    private async Task DownloadModpack(HttpClient httpClient, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(Parameters.Data.SelectedModpack);

        string modpackUrl = string.Empty;

        if (!string.IsNullOrWhiteSpace(Parameters.Data.SelectedModpack.Url))
            modpackUrl = Parameters.Data.SelectedModpack.Url;
        else
            modpackUrl = $"https://files.veloe.link/launcher/modpacks/{Parameters.Data.SelectedModpack.Name}.zip";

        if (!Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}"))
        {
            _logger.Debug("Creating path: {0}", $"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}");
            Directory.CreateDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}");
        }

        _logger.Debug("Downloading: {0}", $"{Parameters.Data.SelectedModpack.Name}.zip");
        Parameters.Action.DownloadingFileName($"{Parameters.Data.SelectedModpack.Name}.zip");
        await DownloadFileAsync(modpackUrl, SettingsService.Instance.MinecraftForlderPath + $"versions/{Parameters.Data.SelectedModpack.Name}.zip", cancellationToken, httpClient);

        FileStream? stream = null;

        if (Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
        {
            foreach (var file in Directory.GetFiles($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
            {
                try { stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None); }
                finally { stream?.Close(); }
            }
        }
        if (Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/config"))
            foreach (var file in Directory.GetFiles($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
            {
                try { stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None); }
                finally { stream?.Close(); }
            }

        stream?.Dispose();

        if (Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
            Directory.Delete($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods", true);
        if (Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/config"))
            Directory.Delete($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/config", true);

        _logger.Debug("Extracting: {0}", $"{Parameters.Data.SelectedModpack.Name}.zip");
        Parameters.Action.DownloadingFileName($"Unpacking {Parameters.Data.SelectedModpack.Name}.zip");
        ZipFile.ExtractToDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}.zip", SettingsService.Instance.MinecraftForlderPath + $"versions/{Parameters.Data.SelectedModpack.Name}", true);
        File.Delete($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}.zip");

        File.WriteAllText($"{SettingsService.Instance.MinecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/revision.json", JsonSerializer.Serialize(Parameters.Data.SelectedModpack.Revision));
    }

    private async Task DownloadForgeOptifine(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        if (!Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/Forge{versionJson.Id}/mods"))
        {
            _logger.Debug("Creating path: {0}", $"{SettingsService.Instance.MinecraftForlderPath}versions/Forge" + versionJson.Id + "/mods");
            Directory.CreateDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/Forge" + versionJson.Id + "/mods");
        }

        _logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.jar");
        Parameters.Action.DownloadingFileName($"Optifine{versionJson.Id}.jar");
        await DownloadFileAsync($@"https://files.veloe.link/launcher/forge/Forge{versionJson.Id}/Optifine{versionJson.Id}.jar", SettingsService.Instance.MinecraftForlderPath + "versions/Forge" + versionJson.Id + "/mods/" + "Optifine" + versionJson.Id + ".jar", cancellationToken, httpClient);
    }

    private async Task DownloadOptifine(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        var optifinePath = $"Optifine{versionJson.Id}";
        var optifineUrl = $"https://files.veloe.link/launcher/optifine/Optifine{versionJson.Id}/";

        if (!Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{optifinePath}"))
        {
            _logger.Debug("Creating path: {0}", $"{SettingsService.Instance.MinecraftForlderPath}versions/" + optifinePath);
            Directory.CreateDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/" + optifinePath);
        }

        _logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.json");
        Parameters.Action.DownloadingFileName($"Optifine{versionJson.Id}.json");
        await DownloadFileAsync($"{optifineUrl}Optifine{versionJson.Id}.json", SettingsService.Instance.MinecraftForlderPath + "versions/" + optifinePath + "/" + optifinePath + ".json", cancellationToken, httpClient);


        _logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.jar");
        Parameters.Action.DownloadingFileName($"Optifine{versionJson.Id}.json");
        await DownloadFileAsync($"{optifineUrl}Optifine{versionJson.Id}.jar", SettingsService.Instance.MinecraftForlderPath + "versions/" + optifinePath + "/" + optifinePath + ".jar", cancellationToken, httpClient);


        _logger.Debug("Downloading: {0}", "libraries.zip");
        Parameters.Action.DownloadingFileName("libraries.zip");
        await DownloadFileAsync(optifineUrl + "libraries.zip", SettingsService.Instance.MinecraftForlderPath + "versions/" + optifinePath + "/libraries.zip", cancellationToken, httpClient);


        _logger.Debug("Extracting: {0}", "libraries.zip");
        Parameters.Action.DownloadingFileName("Unpacking libraries.zip");
        ZipFile.ExtractToDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/Optifine{versionJson.Id}/libraries.zip", SettingsService.Instance.MinecraftForlderPath, true);
        File.Delete($"{SettingsService.Instance.MinecraftForlderPath}versions/Optifine{versionJson.Id}/libraries.zip");
    }

    private async Task DownloadForge(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        var forgePath = $"Forge{versionJson.Id}";
        var forgeUrl = $"https://files.veloe.link/launcher/forge/Forge{versionJson.Id}/";

        if (!Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/Forge{versionJson.Id}"))
        {
            _logger.Debug("Creating path: {0}", $"{SettingsService.Instance.MinecraftForlderPath}versions/" + forgePath);
            Directory.CreateDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/" + forgePath);
        }


        _logger.Debug("Downloading: {0}", $"Forge{versionJson.Id}.json");
        Parameters.Action.DownloadingFileName($"Forge{versionJson.Id}.json");
        await DownloadFileAsync($"{forgeUrl}Forge{versionJson.Id}.json", SettingsService.Instance.MinecraftForlderPath + "versions/" + forgePath + "/" + forgePath + ".json", cancellationToken, httpClient);


        _logger.Debug("Downloading: {0}", "libraries.zip");
        Parameters.Action.DownloadingFileName("libraries.zip");
        await DownloadFileAsync(forgeUrl + "libraries.zip", SettingsService.Instance.MinecraftForlderPath + "versions/" + forgePath + "/libraries.zip", cancellationToken, httpClient);


        _logger.Debug("Extracting: {0}", "libraries.zip");
        Parameters.Action.DownloadingFileName("Unpacking libraries.zip");
        ZipFile.ExtractToDirectory($"{SettingsService.Instance.MinecraftForlderPath}versions/Forge{versionJson.Id}/libraries.zip", SettingsService.Instance.MinecraftForlderPath, true);
        File.Delete($"{SettingsService.Instance.MinecraftForlderPath}versions/Forge{versionJson.Id}/libraries.zip");
    }

    private async Task DownloadAssets(Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        using var httpClient = new HttpClient();
        var assetsJson = await DownloadAndDeserializeJsonData<AssetsManifest>(versionJson.AssetIndex.Url);
        var assetsPath = $"{SettingsService.Instance.MinecraftForlderPath}{(versionJson.Assets == "pre-1.6" ? "resources" : $"assets/{versionJson.Assets}/objects")}";
        var assetsUrl = "https://resources.download.minecraft.net/";

        //download assets json
        _logger.Debug("Downloading: {0}", versionJson.Assets + ".json");

        if (!Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/"))
            Directory.CreateDirectory(SettingsService.Instance.MinecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/");
        await DownloadFileAsync(versionJson.AssetIndex.Url, SettingsService.Instance.MinecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/" + versionJson.Assets + ".json", cancellationToken);

        _logger.Debug("Downloading assets.");
        Parameters.Action.SetProgress(0);
        var assetsCount = assetsJson?.Objects?.Count ?? 1;
        var assetsIterator = 1;

        List<Task> assetsDownloadTasks = new(assetsJson?.Objects?.Count ?? 1024);
        using SemaphoreSlim concurrencySemaphore = new(40, 40);

        foreach (var asset in assetsJson?.Objects?.DistinctBy(a => a.Value.Hash) ?? new Dictionary<string, Asset>())
        {
            assetsDownloadTasks.Add(Task.Run(async () =>
            {
                var isPreVersion = versionJson.Assets == "pre-1.6";

                var folder = isPreVersion ?
                    Path.GetDirectoryName(asset.Key) :
                    asset.Value.Hash[..2];

                var name = isPreVersion ?
                    Path.GetFileName(asset.Key) :
                    asset.Value.Hash;

                var assetRoot = folder.Contains("icons") ? $"{SettingsService.Instance.MinecraftForlderPath}assets/{versionJson.Assets}" : assetsPath;

                var chksum = string.Empty;
                //here hash check
                if (File.Exists(Path.Combine(assetRoot, folder, name)))
                {
                    Parameters.Action.DownloadingFileName($"Checking hash {assetsIterator} of {assetsCount}");
                    using FileStream fop = File.OpenRead(Path.Combine(assetRoot, folder, name));
                    byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                    chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();
                    fop.Close();
                }

                if (chksum != (isPreVersion ? asset.Value.Hash : name))
                {
                    await concurrencySemaphore.WaitAsync(cancellationToken);
                    try
                    {
                        if (!Directory.Exists(assetRoot + "/" + folder))
                            Directory.CreateDirectory(assetRoot + "/" + folder);

                        _logger.Debug("Downloading asset: {0} ", name);
                        //DownloadingFileName($"Asset {assetsIterator} of {assetsCount}");
                        Parameters.Action.DownloadingFileName($"Asset {assetsIterator} of {assetsCount}");
                        await DownloadFileAsync(assetsUrl + asset.Value.Hash[..2] + "/" + asset.Value.Hash, Path.Combine(assetRoot, folder, name), cancellationToken, httpClient);
                    }
                    finally
                    {
                        concurrencySemaphore.Release();
                    }
                }
                Parameters.Action.SetProgress(assetsIterator * 100 / assetsCount);
                Interlocked.Increment(ref assetsIterator);

            }));
        }
        Task.WaitAll(assetsDownloadTasks.ToArray());

    }

    private async Task DownloadLibraries(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        _logger.Debug("Downloading libraries.");

        foreach (var library in versionJson.Libraries)
        {
            var libPath = SettingsService.Instance.MinecraftForlderPath + "libraries/";
            string libUrl = string.Empty;
            string sha1 = string.Empty;
            // getting path and url if universal lib
            if (library.Natives is null)
            {
                libPath += library.Downloads.Artifact.Path;
                libUrl = library.Downloads.Artifact.Url;
                sha1 = library.Downloads.Artifact.Sha1;
            }
            else
            {   // getting path if native
                libUrl = "";
                if (library.Downloads.Classifiers is not null)
                {
                    if (OperatingSystem.IsWindows() && library.Natives.Windows is not null)
                    {
                        if (library.Downloads.Classifiers.NativesWindows is not null)
                        {
                            libPath += library.Downloads.Classifiers.NativesWindows.Path;
                            libUrl = library.Downloads.Classifiers.NativesWindows.Url;
                            sha1 = library.Downloads.Classifiers.NativesWindows.Sha1;
                        }
                        else
                        {
                            if (Environment.Is64BitOperatingSystem && library.Downloads.Classifiers.NativesWindows64 is not null)
                            {
                                libPath += library.Downloads.Classifiers.NativesWindows64.Path;
                                libUrl = library.Downloads.Classifiers.NativesWindows64.Url;
                                sha1 = library.Downloads.Classifiers.NativesWindows64.Sha1;
                            }
                            else if (library.Downloads.Classifiers.NativesWindows32 is not null)
                            {
                                libPath += library.Downloads.Classifiers.NativesWindows32.Path;
                                libUrl = library.Downloads.Classifiers.NativesWindows32.Url;
                                sha1 = library.Downloads.Classifiers.NativesWindows32.Sha1;
                            }
                        }
                    }
                    if (OperatingSystem.IsLinux() && library.Natives.Linux is not null && library.Downloads.Classifiers.NativesLinux is not null)
                    {
                        libPath += library.Downloads.Classifiers.NativesLinux.Path;
                        libUrl = library.Downloads.Classifiers.NativesLinux.Url;
                        sha1 = library.Downloads.Classifiers.NativesLinux.Url;
                    }
                }
            }

            //if no lib url
            if (libUrl == string.Empty)
                continue;

            var libName = Path.GetFileName(libPath);
            var libDir = Path.GetDirectoryName(libPath);

            if (libName is null || libDir is null)
            {
                if (libPath == string.Empty)
                    _logger.Warning("Library dir, name are null, because libraryPath is empty! Skipping...");
                else
                    _logger.Warning("Library dir or name are null! Skipping...");
                continue;
            }

            //checking rules
            if (library.Rules == null)
            {
                if (!Directory.Exists(libDir))
                {
                    _logger.Debug("Creating path: {0}", libDir);
                    Directory.CreateDirectory(libDir);
                }

                var chksum = string.Empty;

                if (File.Exists(libPath))
                {
                    Parameters.Action.DownloadingFileName($"Checking hash: {libName}");
                    FileStream fop = File.OpenRead(libPath);
                    byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                    chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();

                    fop.Close();
                    fop.Dispose();
                }

                if (chksum != sha1)
                {
                    _logger.Debug("Downloading: {0}", libName);
                    Parameters.Action.DownloadingFileName(libName);
                    await DownloadFileAsync(libUrl, libDir + "/" + libName, cancellationToken, httpClient);
                }
            }
            else
            {
                foreach (var rule in library.Rules)
                {
                    if (rule.Action == "allow") // && rule.os is null
                    {
                        if (!Directory.Exists(libDir))
                        {
                            _logger.Debug("Creating path: {0}", libDir);
                            Directory.CreateDirectory(libDir);
                        }

                        var chksum = string.Empty;

                        if (File.Exists(libDir + "/" + libName) && sha1 != string.Empty)
                        {
                            Parameters.Action.DownloadingFileName($"Checking hash: {libName}");
                            FileStream fop = File.OpenRead(libDir + "/" + libName);
                            byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                            chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();

                            fop.Close();
                            fop.Dispose();
                        }

                        if (chksum != sha1)
                        {
                            _logger.Debug("Downloading: {0}", libName);
                            Parameters.Action.DownloadingFileName(libName);
                            await DownloadFileAsync(libUrl, libDir + "/" + libName, cancellationToken, httpClient);
                        }
                        continue;
                    }
                    //fabic on start needs all libraries for all os, so os check removed
                    /*
                    if (rule.action == "allow" && (rule.os.name == "windows" || rule.os.name == "linux" || rule.os.name == "osx" ))
                    {
                        if (!Directory.Exists(libPath))
                        {
                            _logger.Debug("Creating path: {0}", path);
                            Directory.CreateDirectory(libPath);
                        }
                        _logger.Debug("Downloading: {0}", libName);
                        DownloadingFileName = libName;
                        webClient.DownloadFileAsync(new System.Uri(libUrl), libPath + "/" + libName);

                    }
                    */
                }
            }
            //unpacking native libs
            if ((library.Natives is not null || library.Downloads.Classifiers is not null) && File.Exists(libPath)/*&& chksum != sha1*/)
            {
                try
                {
                    if (!(library.Downloads.Classifiers is not null && (
                        library.Downloads.Classifiers.NativesWindows is not null ||
                        library.Downloads.Classifiers.NativesWindows64 is not null ||
                        library.Downloads.Classifiers.NativesWindows32 is not null && OperatingSystem.IsWindows() ||
                        library.Downloads.Classifiers.NativesLinux is not null && OperatingSystem.IsLinux())))
                        continue;

                    if (!Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.Id + "/natives/"))
                    {
                        _logger.Debug("Creating path: {0}", SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
                        Directory.CreateDirectory(SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
                    }
                    _logger.Debug("Extracting {0} to {1}", libName, SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
                    ZipFile.ExtractToDirectory(libDir + "/" + libName, SettingsService.Instance.MinecraftForlderPath + "versions/" + versionJson.Id + "/natives/", true);
                }
                catch (IOException)
                {
                    _logger.Error("IOException on native lib extraction");
                }
                //TODO delete META-INF and sha1 files after
            }
        }
    }

    private async Task DownloadMainJar(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        //download json
        var path = SettingsService.Instance.MinecraftForlderPath + "/versions/" + versionJson.Id;

        //download jar
        if (!Directory.Exists(path))
        {
            _logger.Debug("Creating path: {0}", path);
            Directory.CreateDirectory(path);
        }

        string chksum = string.Empty;
        //here hash check
        if (File.Exists(path + "/" + versionJson.Id + ".jar"))
        {
            Parameters.Action.DownloadingFileName($"Checking hash: {versionJson.Id}.jar");
            FileStream fop = File.OpenRead(path + "/" + versionJson.Id + ".jar");
            byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
            chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();

            fop.Close();
            fop.Dispose();
        }

        if (chksum != versionJson.Downloads.Client.Sha1)
        {
            _logger.Debug("Downloading {0}", $"{versionJson.Id}.jar");
            Parameters.Action.DownloadingFileName($"{versionJson.Id}.jar");
            await DownloadFileAsync(versionJson.Downloads.Client.Url, path + "/" + versionJson.Id + ".jar", cancellationToken, httpClient);
        }
    }

    private async Task DownloadJava(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
    {
        _logger.Debug("Getting required Java version.");

        var javaVersion = versionJson.JavaVersion?.MajorVersion ??
            (versionJson.Assets == "legacy" ? 8 :
            throw new ArgumentException("Required Java version was not found in json file."));

        var javaUrl = $"https://files.veloe.link/launcher/java/{javaVersion}/{(OperatingSystem.IsWindows() ? "windows" : "linux")}{(Environment.Is64BitOperatingSystem ? "64" : "32")}/java.zip";

        if (await IsFileAvaliable(javaUrl, cancellationToken))
        {
            _logger.Debug("Downloading Java");
            Parameters.Action.DownloadingFileName("java.zip");
            await DownloadFileAsync(javaUrl, SettingsService.Instance.MinecraftForlderPath + "java.zip", cancellationToken, httpClient);

            _logger.Debug("Unpacking Java");
            Parameters.Action.DownloadingFileName("Unpacking java.zip");
            ZipFile.ExtractToDirectory($"{SettingsService.Instance.MinecraftForlderPath}java.zip", SettingsService.Instance.MinecraftForlderPath, true);
            File.Delete($"{SettingsService.Instance.MinecraftForlderPath}java.zip");
        }
        else
            throw new HttpRequestException("Required Java version was not found on server.");
    }

    private async Task<Entity.Version.Version?> GetVersionJson()
    {
        ArgumentNullException.ThrowIfNull(Parameters.Data.SelectedVersion);
        
        if (Parameters.Data.SelectedModpack is not null)
        {
            ArgumentNullException.ThrowIfNull(Parameters.Data.SelectedModpack);

            if (Parameters.Data.SelectedVersion.Id != Parameters.Data.SelectedModpack.Version)
                throw new ArgumentException("Selected version id not equal selected modpack version!");

            Parameters.Parameter.InstallForge = Parameters.Data.SelectedModpack.Forge;
            Parameters.Parameter.InstallForgeOptifine = Parameters.Data.SelectedModpack.ForgeOptifine;
            Parameters.Parameter.InstallOptifine = Parameters.Data.SelectedModpack.Optifine;
            Parameters.Parameter.InstallFabric = Parameters.Data.SelectedModpack.Fabric;

            _logger.Debug("Downloading {0}.json", Parameters.Data.SelectedVersion.Id);
            Parameters.Action.DownloadingFileName($"{Parameters.Data.SelectedVersion.Id}.json");
            return await DownloadAndDeserializeJsonData<Entity.Version.Version>(Parameters.Data.SelectedVersion.Url, SettingsService.Instance.MinecraftForlderPath + "versions/" + Parameters.Data.SelectedVersion.Id + "/", Parameters.Data.SelectedVersion.Id + ".json");
        }
        else
        {
            Parameters.Action.DownloadingFileName($"{Parameters.Data.SelectedVersion.Id}.json");
            _logger.Debug("Downloading {0}.json", Parameters.Data.SelectedVersion.Id);
            return await DownloadAndDeserializeJsonData<Entity.Version.Version>(Parameters.Data.SelectedVersion.Url, SettingsService.Instance.MinecraftForlderPath + "versions/" + Parameters.Data.SelectedVersion.Id + "/", Parameters.Data.SelectedVersion.Id + ".json");
        }
    }

    public static async Task<T?> DownloadAndDeserializeJsonData<T>(string url, string path = "", string filename = "", Serilog.ILogger? logger = null) where T : new()
    {
        string jsonData;

        if (logger is null) logger = SettingsService.logger;

        try
        {
            logger.Debug($"Downloading: {url.Split('/').Last()}");
            jsonData = await _sHttpClient.GetStringAsync(url);
        }
        catch (Exception ex)
        {
            throw new WebException($"An error occured while downloading {url}. Check your internet connection.", ex);
        }

        if (string.IsNullOrEmpty(jsonData))
        {
            logger.Warning("Empty string!");
            return new T();
        }
        else
        {
            if (!string.IsNullOrEmpty(filename) && !string.IsNullOrEmpty(path))
            {
                if (!Directory.Exists(path))
                {
                    logger.Debug("Creating path: {0}", path);
                    Directory.CreateDirectory(path);
                }
                File.WriteAllText(path + filename, jsonData);
            }
            //_logger.Debug("Return serialized string.");
            return JsonSerializer.Deserialize<T>(jsonData, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
        }
    }

    public static async Task DownloadFileAsync(string url, string path, CancellationToken ct, HttpClient? client = null)
    {
        client ??= _sHttpClient;
        using HttpResponseMessage response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseHeadersRead, ct);
        response.EnsureSuccessStatusCode();
        using Stream contentStream = await response.Content.ReadAsStreamAsync(ct);
        using FileStream fileStream = File.OpenWrite(path);
        await contentStream.CopyToAsync(fileStream, ct);
    }

    public static async Task<bool> IsFileAvaliable(string url, CancellationToken ct)
    {
        using HttpResponseMessage response = await _sHttpClient.GetAsync(new Uri(url), HttpCompletionOption.ResponseHeadersRead, ct);
        return response.IsSuccessStatusCode;
    }

    public async Task DownloadFabricLibraries(CancellationToken ct)
    {
        var handler = new HttpClientHandler() { AllowAutoRedirect = true };
        var ph = new ProgressMessageHandler(handler);
        ph.HttpSendProgress += (_, args) =>
        {
            Parameters.Action.SetProgress(args.ProgressPercentage);
        };

        ph.HttpReceiveProgress += (_, args) =>
        {
            Parameters.Action.SetProgress(args.ProgressPercentage);
        };

        using var httpClient = new HttpClient(ph);

        ArgumentNullException.ThrowIfNull(Parameters.Data.VersionJson);

        foreach (var library in Parameters.Data.VersionJson.Libraries)
        {
            if (string.IsNullOrEmpty(library.Name) || string.IsNullOrEmpty(library.Url))
                continue;

            var relativePath = StartCommandBuilder.GetLibRelativePathFromName(library.Name);
            var libPath = SettingsService.Instance.MinecraftForlderPath + "libraries/" + relativePath;
            var dirPath = Path.GetDirectoryName(libPath);

            ArgumentNullException.ThrowIfNullOrEmpty(dirPath);

            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }

            // some links may be unawailable
            if (await IsFileAvaliable($"{library.Url}{relativePath}", ct))
                await DownloadFileAsync($"{library.Url}{relativePath}", libPath, ct, httpClient);
            else
                _logger.Warning($"Library {library.Name} url is not accessible. Skipping...");

        }
    }
}
