using System.Buffers;
using System.Net.Http;
using Млаумчерб.Клиент.видимое;
using Млаумчерб.Клиент.классы;
using Timer = DTLib.Timer;

namespace Млаумчерб.Клиент;

public static class Сеть
{
    private static HttpClient http = new();
    private const string ASSET_SERVER_URL = "https://resources.download.minecraft.net/";
    private static readonly string[] VERSION_MANIFEST_URLS = {
        "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
    };

    public static async Task DownloadFileHTTP(string url, IOPath outPath,
        Action<ArraySegment<byte>>? transformFunc = null, CancellationToken ct = default)
    {
        await using var src = await http.GetStreamAsync(url, ct);
        await using var dst = File.OpenWrite(outPath);
        
        await src.CopyTransformAsync(dst, transformFunc, ct).ConfigureAwait(false);
    }
    
    public static async Task CopyTransformAsync(this Stream src, Stream dst,
        Action<ArraySegment<byte>>? transformFunc = null, CancellationToken ct = default)
    {
        // default dotnet runtime buffer size
        int bufferSize = 81920;
        byte[] readBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
        byte[] writeBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
        try
        {
            var readTask = src.ReadAsync(readBuffer, 0, bufferSize, ct).ConfigureAwait(false);
            while (true)
            {
                int readCount = await readTask;
                if (readCount == 0)
                    break;
                (readBuffer, writeBuffer) = (writeBuffer, readBuffer);
                readTask = src.ReadAsync(readBuffer, 0, bufferSize, ct).ConfigureAwait(false);
                transformFunc?.Invoke(new  ArraySegment<byte>(writeBuffer, 0, readCount));
                dst.Write(writeBuffer, 0, readCount);
            }
        }
        catch (OperationCanceledException)
        { }
        finally
        {
            ArrayPool<byte>.Shared.Return(readBuffer);
            ArrayPool<byte>.Shared.Return(writeBuffer);
        }
    }
    
    public static async Task DownloadAssets(AssetIndexProperties assetIndexProperties, CancellationToken ct, bool force)
    {
        IOPath indexFilePath = Пути.GetAssetIndexFilePath(assetIndexProperties.id);
        if (File.Exists(indexFilePath) && !force)
            return;
        
        IOPath indexFilePathTmp = indexFilePath + ".tmp";
        if(File.Exists(indexFilePathTmp))
            File.Delete(indexFilePathTmp);
        
        // TODO: add something to Downloads ScrollList
        Приложение.Логгер.LogInfo(nameof(DownloadAssets), $"started downloading asset index to '{indexFilePathTmp}'");
        await DownloadFileHTTP(assetIndexProperties.url, indexFilePathTmp, null, ct);
        Приложение.Логгер.LogInfo(nameof(DownloadAssets), "finished downloading asset index");
        
        string indexFileText = File.ReadAllText(indexFilePathTmp);
        AssetIndex assetIndex = JsonConvert.DeserializeObject<AssetIndex>(indexFileText)
            ?? throw new Exception($"can't deserialize asset index file '{indexFilePathTmp}'");

        var assets = new List<KeyValuePair<string, AssetProperties>>();
        HashSet<string> assetHashes = new HashSet<string>();
        long totalSize = 0;
        long currentSize = 0;
        foreach (var pair in assetIndex.objects)
        {
            if (!assetHashes.Contains(pair.Value.hash))
            {
                assets.Add(pair);
                assetHashes.Add(pair.Value.hash);
                totalSize += pair.Value.size;
            }
        }

        void AddBytesCountAtomic(ArraySegment<byte> chunk)
        {
            long chunkSize = chunk.Count;
            Interlocked.Add(ref currentSize, chunkSize);
        }

        long prevSize = 0;
        int timerDelay = 2000;
        void ReportProgress()
        {
            // TODO: add something to Downloads ScrollList
            long totalSizeM = totalSize/(1024*1024);
            long currentSizeM = currentSize/(1024*1024);
            long bytesPerSec = (currentSize - prevSize) / (timerDelay / 1000);
            float KbytesPerSec = bytesPerSec / 1024f;
            prevSize = currentSize;
            Приложение.Логгер.LogDebug(nameof(DownloadAssets), 
                $"download progress {currentSizeM}Mb/{totalSizeM}Mb ({KbytesPerSec}Kb/s)");
        }
        using Timer timer = new Timer(true, timerDelay, ReportProgress);
        timer.Start();

        Приложение.Логгер.LogInfo(nameof(DownloadAssets), "started downloading assets");
        int parallelDownloads = 32;
        var tasks = new Task[parallelDownloads];
        var currentlyDownloadingFileHashes = new string[parallelDownloads];
        int i = 0;
        foreach (var a in assets)
        {
            string hash = a.Value.hash;
            string hashStart = hash.Substring(0, 2);
            var assetUrl = $"{ASSET_SERVER_URL}/{hashStart}/{hash}";
            IOPath assetFilePath = Path.Concat("assets", "objects", hashStart, hash);
            Приложение.Логгер.LogDebug(nameof(DownloadAssets), $"downloading asset '{a.Key}' {hash}");
            tasks[i] = DownloadFileHTTP(assetUrl, assetFilePath, AddBytesCountAtomic, ct);
            currentlyDownloadingFileHashes[i] = hash;
            if (++i == parallelDownloads)
            {
                await Task.WhenAll(tasks);
                i = 0;
            }
        }

        await Task.WhenAll(tasks);
        timer.Stop();
        timer.InvokeAction();
        File.Move(indexFilePathTmp, indexFilePath, true);
        Приложение.Логгер.LogInfo(nameof(DownloadAssets), "finished downloading assets");
    }

    private static async Task<List<RemoteVersionDescriptorProps>> GetRemoteVersionDescriptorsAsync()
    {
        List<RemoteVersionDescriptorProps> descriptors = new();
        foreach (var url in VERSION_MANIFEST_URLS)
        {
            try
            {
                var manifestText = await http.GetStringAsync(url);
                var catalog = JsonConvert.DeserializeObject<VersionCatalog>(manifestText);
                if (catalog != null)
                    descriptors.AddRange(catalog.versions);
            }
            catch (Exception ex)
            {
                Приложение.Логгер.LogWarn(nameof(Сеть), ex);
            }
        }
        return descriptors;
    }

    private static List<GameVersionProps>? _versionPropsList;
    /// <returns>empty list if couldn't find any remote versions</returns>
    public static async Task<IReadOnlyList<GameVersionProps>> GetDownloadableVersions()
    {
        if(_versionPropsList == null)
        {
            _versionPropsList = new();
            var rvdlist = await GetRemoteVersionDescriptorsAsync();
            foreach (var r in rvdlist)
            {
                if(r.type == "release")
                    _versionPropsList.Add(new GameVersionProps(r.id, r.url));
            }
        }
        return _versionPropsList;
    }

    public static async Task DownloadVersionFile(string url, IOPath filePath, bool force)
    {
        if(File.Exists(filePath) && !force)
            return;
        await DownloadFileHTTP(url, filePath);
    }

    public static async Task DownloadJava(JavaVersion javaVersion, IOPath path, bool force)
    {
        
    }

    public static async Task DownloadLibraries(List<Library> libraries, IOPath librariesDir, bool force)
    {
        
    }
}