using System.Net;
using System.Net.Http;
using Mlaumcherb.Client.Avalonia.зримое;
using Mlaumcherb.Client.Avalonia.классы;
using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.сеть.NetworkHelper;

namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;

public class AssetsDownloadTaskFactory : INetworkTaskFactory
{
    private const string ASSET_SERVER_URL = "https://resources.download.minecraft.net/";
    private GameVersionDescriptor _descriptor;
    private IOPath _indexFilePath;
    List<AssetDownloadProperties> _assetsToDownload = new();

    public AssetsDownloadTaskFactory(GameVersionDescriptor descriptor)
    {
        _descriptor = descriptor;
        _indexFilePath = PathHelper.GetAssetIndexFilePath(_descriptor.assetIndex.id);
    }
    
    public async Task<NetworkTask?> CreateAsync(bool checkHashes)
    {
        NetworkTask? networkTask = null;
        if (!await CheckFilesAsync(checkHashes))
        {
            networkTask = new NetworkTask(
                $"assets '{_descriptor.assetIndex.id}'",
                GetTotalSize(),
                Download
            );
        }
        
        return networkTask;
    }
    private async Task<bool> CheckFilesAsync(bool checkHashes)
    {
        (AssetIndex assetIndex, _) = await ReadOrDownloadAndDeserialize<AssetIndex>(
            _indexFilePath,
            _descriptor.assetIndex.url, 
            _descriptor.assetIndex.sha1,
            checkHashes);
        
        // removing duplicates for Dictionary (idk how can it be possible, but Newtonsoft.Json creates them)
        HashSet<string> assetHashes = new HashSet<string>();
        foreach (var pair in assetIndex.objects)
        {
            if (assetHashes.Add(pair.Value.hash))
            {
                var a = new AssetDownloadProperties(pair.Key, pair.Value);
                if (!HashHelper.CheckFileSHA1(a.filePath, a.hash, checkHashes))
                {
                    _assetsToDownload.Add(a);
                }
            }
        }
        
        return _assetsToDownload.Count == 0;
    }

    private long GetTotalSize()
    {
        long totalSize = 0;
        foreach (var a in _assetsToDownload) 
            totalSize += a.size;
        return totalSize;
    }

    private class AssetDownloadProperties
    {
        public string name;
        public string hash;
        public long size;
        public string url;
        public IOPath filePath;

        public AssetDownloadProperties(string key, AssetProperties p)
        {
            name = key;
            hash = p.hash;
            size = p.size;
            string hashStart = hash.Substring(0, 2);
            url = $"{ASSET_SERVER_URL}/{hashStart}/{hash}";
            filePath = Path.Concat(IOPath.ArrayCast(["assets", "objects", hashStart, hash], true));
        }
    }
    
    private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
    {
        LauncherApp.Logger.LogInfo(nameof(NetworkHelper), $"started downloading assets '{_descriptor.assetIndex.id}'");
        ParallelOptions opt = new()
        {
            MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
            CancellationToken = ct
        };
        await Parallel.ForEachAsync(_assetsToDownload, opt, 
        async (a, _ct) =>
        {
            bool completed = false;
            while(!completed)
            {
                LauncherApp.Logger.LogDebug(nameof(NetworkHelper), $"downloading asset '{a.name}' {a.hash}");
                try
                {
                    await DownloadFile(a.url, a.filePath, _ct, pr.AddBytesCount);
                    completed = true;
                }
                catch (HttpRequestException httpException)
                {
                    // wait on rate limit
                    if(httpException.StatusCode == HttpStatusCode.TooManyRequests)
                    {
                        LauncherApp.Logger.LogDebug(nameof(NetworkHelper), "rate limit hit");
                        await Task.Delay(1000, _ct);
                    }
                    else throw;
                }
            }
        });
        LauncherApp.Logger.LogInfo(nameof(NetworkHelper), $"finished downloading assets '{_descriptor.assetIndex.id}'");
    }
}