using System.Net.Http.Headers;
using System.Net.Mime;

namespace ServiceLib.Helper;

/// <summary>
/// </summary>
public class HttpClientHelper
{
    private static readonly Lazy<HttpClientHelper> _instance = new(() =>
    {
        SocketsHttpHandler handler = new() { UseCookies = false };
        HttpClientHelper helper = new(new HttpClient(handler));
        return helper;
    });

    public static HttpClientHelper Instance => _instance.Value;
    private readonly HttpClient httpClient;

    private HttpClientHelper(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public async Task<string?> TryGetAsync(string url)
    {
        if (url.IsNullOrEmpty())
        {
            return null;
        }

        try
        {
            var response = await httpClient.GetAsync(url);
            return await response.Content.ReadAsStringAsync();
        }
        catch
        {
            return null;
        }
    }

    public async Task<string?> GetAsync(string url)
    {
        if (url.IsNullOrEmpty())
        {
            return null;
        }
        return await httpClient.GetStringAsync(url);
    }

    public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
    {
        if (url.IsNullOrEmpty())
        {
            return null;
        }
        return await client.GetStringAsync(url, token);
    }

    public async Task PutAsync(string url, Dictionary<string, string> headers)
    {
        var jsonContent = JsonUtils.Serialize(headers);
        var content = new StringContent(jsonContent, Encoding.UTF8, MediaTypeNames.Application.Json);

        await httpClient.PutAsync(url, content);
    }

    public async Task PatchAsync(string url, Dictionary<string, string> headers)
    {
        var myContent = JsonUtils.Serialize(headers);
        var buffer = Encoding.UTF8.GetBytes(myContent);
        var byteContent = new ByteArrayContent(buffer);
        byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        await httpClient.PatchAsync(url, byteContent);
    }

    public async Task DeleteAsync(string url)
    {
        await httpClient.DeleteAsync(url);
    }

    public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default)
    {
        ArgumentNullException.ThrowIfNull(url);
        ArgumentNullException.ThrowIfNull(fileName);
        if (File.Exists(fileName))
        {
            File.Delete(fileName);
        }

        using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);

        if (!response.IsSuccessStatusCode)
        {
            throw new Exception(response.StatusCode.ToString());
        }

        var total = response.Content.Headers.ContentLength ?? -1L;
        var canReportProgress = total != -1 && progress != null;

        await using var stream = await response.Content.ReadAsStreamAsync(token);
        await using var file = File.Create(fileName);
        var totalRead = 0L;
        var buffer = new byte[1024 * 1024];
        var progressPercentage = 0;

        while (true)
        {
            token.ThrowIfCancellationRequested();

            var read = await stream.ReadAsync(buffer, token);
            totalRead += read;

            if (read == 0)
            {
                break;
            }
            await file.WriteAsync(buffer.AsMemory(0, read), token);

            if (canReportProgress)
            {
                var percent = (int)(100.0 * totalRead / total);
                //if (progressPercentage != percent && percent % 10 == 0)
                {
                    progressPercentage = percent;
                    progress?.Report(percent);
                }
            }
        }
        if (canReportProgress)
        {
            progress?.Report(101);
        }
    }

    public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress<string> progress, CancellationToken token = default)
    {
        if (url.IsNullOrEmpty())
        {
            throw new ArgumentNullException(nameof(url));
        }

        var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);

        if (!response.IsSuccessStatusCode)
        {
            throw new Exception(response.StatusCode.ToString());
        }

        //var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
        //var canReportProgress = total != -1 && progress != null;

        await using var stream = await response.Content.ReadAsStreamAsync(token);
        var totalRead = 0L;
        var buffer = new byte[1024 * 64];
        var isMoreToRead = true;
        var progressSpeed = string.Empty;
        var totalDatetime = DateTime.Now;
        var totalSecond = 0;

        do
        {
            if (token.IsCancellationRequested)
            {
                if (totalRead > 0)
                {
                    return;
                }
                else
                {
                    token.ThrowIfCancellationRequested();
                }
            }

            var read = await stream.ReadAsync(buffer, token);

            if (read == 0)
            {
                isMoreToRead = false;
            }
            else
            {
                var data = new byte[read];
                buffer.ToList().CopyTo(0, data, 0, read);

                totalRead += read;

                var ts = DateTime.Now - totalDatetime;
                if (progress != null && ts.Seconds > totalSecond)
                {
                    totalSecond = ts.Seconds;
                    var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0");
                    if (progressSpeed != speed)
                    {
                        progressSpeed = speed;
                        progress.Report(speed);
                    }
                }
            }
        } while (isMoreToRead);
    }

    public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
    {
        var responseTime = -1;
        try
        {
            using var cts = new CancellationTokenSource();
            cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
            using var client = new HttpClient(new SocketsHttpHandler()
            {
                Proxy = webProxy,
                UseProxy = webProxy != null
            });

            List<int> oneTime = new();
            for (var i = 0; i < 2; i++)
            {
                var timer = Stopwatch.StartNew();
                await client.GetAsync(url, cts.Token).ConfigureAwait(false);
                timer.Stop();
                oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
                await Task.Delay(100);
            }
            responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
        }
        catch //(Exception ex)
        {
            //Utile.SaveLog(ex.Message, ex);
        }
        return responseTime;
    }
}
