using Newtonsoft.Json;
using VoidCat.Model;
using VoidCat.Services.Abstractions;

namespace VoidCat.Services.Stats;

/// <summary>
/// Fetch stats from Prometheus
/// </summary>
public class PrometheusStatsReporter : ITimeSeriesStatsReporter
{
    private readonly ILogger<PrometheusStatsReporter> _logger;
    private readonly HttpClient _client;
    private readonly VoidSettings _settings;

    public PrometheusStatsReporter(ILogger<PrometheusStatsReporter> logger, HttpClient client, VoidSettings settings)
    {
        _client = client;
        _settings = settings;
        _logger = logger;

        _client.BaseAddress = settings.Prometheus!.Url;
    }

    public async ValueTask<IReadOnlyList<BandwidthPoint>> GetBandwidth(DateTime start, DateTime end)
    {
        return await QueryInner(string.Format(_settings.Prometheus?.EgressQuery ?? string.Empty, string.Empty), start, end);
    }

    public async ValueTask<IReadOnlyList<BandwidthPoint>> GetBandwidth(Guid id, DateTime start, DateTime end)
    {
        return await QueryInner(string.Format(_settings.Prometheus?.EgressQuery ?? string.Empty, id.ToString()), start, end);
    }

    private async Task<IReadOnlyList<BandwidthPoint>> QueryInner(string query, DateTime start, DateTime end)
    {
        var res = await QueryRange(query, start, end, TimeSpan.FromHours(24));

        var bp = new List<BandwidthPoint>();
        foreach (var r in res.Data.Result)
        {
            foreach (var v in r.Values)
            {
                bp.Add(new(DateTimeOffset.FromUnixTimeSeconds((long) v[0])
                        .DateTime, 0ul,
                    (ulong) decimal.Parse(v[1] as string ?? "0")));
            }
        }

        return bp;
    }

    private async Task<Metrics?> QueryRange(string query, DateTimeOffset start, DateTimeOffset end, TimeSpan step)
    {
        var url =
            $"/api/v1/query_range?query={Uri.EscapeDataString(query)}&start={start.ToUnixTimeSeconds()}&end={end.ToUnixTimeSeconds()}&step={(int) step.TotalSeconds}";
        var req = await _client.SendAsync(new(HttpMethod.Get, url));
        if (req.IsSuccessStatusCode)
        {
            var json = await req.Content.ReadAsStringAsync();
            if (!string.IsNullOrEmpty(json))
            {
                return JsonConvert.DeserializeObject<Metrics>(json);
            }
        }
        else
        {
            _logger.LogWarning("Failed to fetch metrics: {Url} {Status}", url, req.StatusCode);
        }

        return default;
    }

    private class Metrics
    {
        [JsonProperty("status")] public string Status { get; set; }

        [JsonProperty("data")] public MetricData Data { get; set; }

        public class MetricData
        {
            [JsonProperty("resultType")] public string ResultType { get; set; }

            [JsonProperty("result")] public List<Result> Result { get; set; }
        }

        public class Metric
        {
            [JsonProperty("file")] public string File { get; set; }

            [JsonProperty("instance")] public string Instance { get; set; }

            [JsonProperty("job")] public string Job { get; set; }
        }

        public class Result
        {
            [JsonProperty("metric")] public Metric Metric { get; set; }

            [JsonProperty("values")] public List<List<object>> Values { get; set; }
        }
    }
}