using System.IO;
using System.Net.Http;
using Downloader;
using PCL.Core.IO.Net;


namespace PCL.Network;

public static class FileDownloader
{
    public static async Task DownloadAsync(string url, string localPath, bool useBrowserUserAgent = false,
        string customUserAgent = "", CancellationToken cancellationToken = default,
        bool enableParallelChunks = true, DownloadFile? trackedFile = null)
    {
        await DownloadCoreAsync([url], localPath, useBrowserUserAgent, customUserAgent, cancellationToken,
            enableParallelChunks, trackedFile).ConfigureAwait(false);
    }

    public static async Task DownloadAsync(IEnumerable<string> urls, string localPath, bool useBrowserUserAgent = false,
        string customUserAgent = "", CancellationToken cancellationToken = default,
        bool enableParallelChunks = true, DownloadFile? trackedFile = null)
    {
        await DownloadCoreAsync(urls, localPath, useBrowserUserAgent, customUserAgent, cancellationToken,
            enableParallelChunks, trackedFile).ConfigureAwait(false);
    }

    public static void DownloadByLoader(string url, string localPath, bool useBrowserUserAgent = false,
        string customUserAgent = "")
    {
        DownloadAsync(url, localPath, useBrowserUserAgent, customUserAgent).GetAwaiter().GetResult();
    }

    public static void DownloadByLoader(IEnumerable<string> urls, string localPath, bool useBrowserUserAgent = false,
        string customUserAgent = "")
    {
        DownloadAsync(urls, localPath, useBrowserUserAgent, customUserAgent).GetAwaiter().GetResult();
    }

    private static async Task DownloadCoreAsync(IEnumerable<string> urls, string localPath, bool useBrowserUserAgent,
        string customUserAgent, CancellationToken cancellationToken, bool enableParallelChunks, DownloadFile? trackedFile)
    {
        var urlList = urls.Select(url => RequestSigning.SecretCdnSign(url.Trim())).Where(url => !string.IsNullOrWhiteSpace(url))
            .Distinct().ToList();
        if (urlList.Count == 0)
            throw new ArgumentException("未提供可用的下载地址", nameof(urls));

        Directory.CreateDirectory(Path.GetDirectoryName(localPath) ?? throw new ArgumentException("下载路径无效", nameof(localPath)));

        Exception? lastException = null;
        foreach (var url in urlList)
        {
            try
            {
                await DownloadSingleAsync(url, localPath, useBrowserUserAgent, customUserAgent, cancellationToken,
                    enableParallelChunks, trackedFile).ConfigureAwait(false);
                return;
            }
            catch (OperationCanceledException)
            {
                CleanupTempFiles(localPath);
                throw;
            }
            catch (Exception ex)
            {
                lastException = ex;
                CleanupTempFiles(localPath);
                ModBase.Log(ex, $"[Download] 下载失败，尝试下一个源：{url}", ModBase.LogLevel.Debug);
            }
        }

        throw new IOException($"下载失败：{localPath}", lastException);
    }

    private static async Task DownloadSingleAsync(string url, string localPath, bool useBrowserUserAgent,
        string customUserAgent, CancellationToken cancellationToken, bool enableParallelChunks, DownloadFile? trackedFile)
    {
        ModBase.Log($"[Download] 开始下载：{url} -> {localPath}");
        CleanupTempFiles(localPath);

        var perFileThreadLimit = enableParallelChunks ? Math.Max(1, ModNet.NetTaskThreadLimit) : 1;
        // 限制最大分块数，防止大文件下载时内存爆炸
        var chunkCount = Math.Min(perFileThreadLimit, 4);
        var configuration = new DownloadConfiguration
        {
            ChunkCount = chunkCount,
            ParallelCount = chunkCount,
            ParallelDownload = chunkCount > 1,
            MaximumBytesPerSecond = ModNet.NetTaskSpeedLimitHigh > 0 ? ModNet.NetTaskSpeedLimitHigh : 0,
            MaxTryAgainOnFailure = 2,
            BlockTimeout = 60000,
            DownloadFileExtension = ModNet.netDownloadEnd,
            EnableAutoResumeDownload = false,
            CustomHttpClientFactory = () => GetHttpClient(url),
            MinimumSizeOfChunking = 1024 * 1024L,
            MaximumMemoryBufferBytes = 256L * 1024 * 1024,
        };

        using var downloader = new DownloadService(configuration);
        using var cancelReg = cancellationToken.Register(() =>
        {
            try { downloader.CancelAsync(); } catch {  } // 忽略
        });
        var tcs = new TaskCompletionSource<bool>();
        void UpdateDownloadStat(DownloadProgressChangedEventArgs args)
        {
            if (trackedFile is null)
                return;

            trackedFile.State = PCL.Network.NetState.Downloading;
            trackedFile.TotalSize = Math.Max(trackedFile.TotalSize, args.TotalBytesToReceive);
            trackedFile.IsUnknownSize = trackedFile.TotalSize <= 0;
            trackedFile.DownloadedBytes = Math.Max(trackedFile.DownloadedBytes, args.ReceivedBytesSize);
            trackedFile.Speed = Math.Max(0L, (long)Math.Round(args.BytesPerSecondSpeed));
            trackedFile.ActiveThreads = Math.Max(0, args.ActiveChunks);
        }

        downloader.DownloadStarted += (_, args) =>
        {
            if (trackedFile is null)
                return;

            trackedFile.State = PCL.Network.NetState.Reading;
            trackedFile.TotalSize = Math.Max(trackedFile.TotalSize, args.TotalBytesToReceive);
            trackedFile.IsUnknownSize = args.TotalBytesToReceive <= 0;
            trackedFile.DownloadedBytes = 0;
            trackedFile.Speed = 0;
            trackedFile.ActiveThreads = 0;
        };
        downloader.DownloadProgressChanged += (_, args) => UpdateDownloadStat(args);
        downloader.ChunkDownloadProgressChanged += (_, args) => UpdateDownloadStat(args);
        downloader.DownloadFileCompleted += (_, args) =>
        {
            if (trackedFile is not null)
            {
                trackedFile.Speed = 0;
                trackedFile.ActiveThreads = 0;
                trackedFile.DownloadedBytes = Math.Max(trackedFile.DownloadedBytes, trackedFile.TotalSize);
            }

            if (args.Cancelled)
                tcs.TrySetCanceled();
            else if (args.Error != null)
                tcs.TrySetException(args.Error);
            else
                tcs.TrySetResult(true);
        };
        try
        {
            await downloader.DownloadFileTaskAsync(url, localPath, cancellationToken).ConfigureAwait(false);
            await tcs.Task.ConfigureAwait(false);
            var tempPath = localPath + ModNet.netDownloadEnd;
            if (!File.Exists(localPath) && File.Exists(tempPath))
            {
                for (var retry = 0; retry < 5; retry++)
                {
                    try
                    {
                        File.Move(tempPath, localPath, true);
                        break;
                    }
                    catch (IOException)
                    {
                        Thread.Sleep(100);
                    }
                }
            }
            if (!File.Exists(localPath))
                throw new IOException($"下载未产生任何文件：{localPath}");
            ModBase.Log($"[Download] 下载成功：{localPath}");
        }
        catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
        {
            throw new OperationCanceledException(cancellationToken);
        }
        catch (TaskCanceledException ex)
        {
            throw new TimeoutException($"下载超时（{url}）", ex);
        }
        catch (OperationCanceledException)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw new IOException($"下载失败：{url}", ex);
        }
    }

    private static void CleanupTempFiles(string localPath)
    {
        var tempPath = localPath + ModNet.netDownloadEnd;
        TryDeleteFile(localPath);
        TryDeleteFile(tempPath);
    }

    private static void TryDeleteFile(string path)
    {
        for (var retry = 0; retry < 5; retry++)
        {
            try
            {
                if (File.Exists(path))
                    File.Delete(path);
                return;
            }
            catch (IOException)
            {
                Thread.Sleep(100);
            }
        }
    }

    private static HttpClient GetHttpClient(string url)
    {
        if (Uri.TryCreate(url, UriKind.Absolute, out var parsedUri)
            && parsedUri.Host is "edge.forgecdn.net" or "mediafilez.forgecdn.net" or "forgecdn.net" or "api.curseforge.com")
        {
            return NetworkService.GetClient(NetworkService.CurseForgeApi);
        }
        
        return NetworkService.GetClient();
    }
}
