using Prometheus;
using System.Diagnostics;

namespace Sample.Web;

/// <summary>
/// Sample background service demonstrating how to publish metrics from background business logic.
/// </summary>
public sealed class SampleService : BackgroundService
{
    /// <summary>
    /// When we allocate HTTP clients, we mark them with this string to label the metrics generated by these HTTP clients.
    /// </summary>
    public const string HttpClientName = "SampleServiceHttpClient";

    public SampleService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    private readonly IHttpClientFactory _httpClientFactory;

    protected override async Task ExecuteAsync(CancellationToken cancel)
    {
        try
        {
            while (!cancel.IsCancellationRequested)
            {
                try
                {
                    await ReadySetGoAsync(cancel);
                }
                catch
                {
                    // Something failed? OK, whatever. We will just try again.
                }

                await Task.Delay(TimeSpan.FromSeconds(1), cancel);
            }
        }
        catch (OperationCanceledException) when (cancel.IsCancellationRequested)
        {
        }
    }

    private async Task ReadySetGoAsync(CancellationToken cancel)
    {
        // Our job is to compare the speed at which google.com loads against the speed microsoft.com loads.
        var httpClient = _httpClientFactory.CreateClient(HttpClientName);

        var googleStopwatch = Stopwatch.StartNew();
        var microsoftStopwatch = Stopwatch.StartNew();

        const string googleUrl = "https://google.com";
        const string microsoftUrl = "https://microsoft.com";

        var googleTask = Task.Run(async delegate
        {
            using var response = await httpClient.GetAsync(googleUrl, cancel);
            googleStopwatch.Stop();
        }, cancel);

        var microsoftTask = Task.Run(async delegate
        {
            using var response = await httpClient.GetAsync(microsoftUrl, cancel);
            microsoftStopwatch.Stop();
        }, cancel);

        await Task.WhenAll(googleTask, microsoftTask);

        var exemplar = Exemplar.From(Exemplar.Pair("traceID", "1234"));
        
        // Determine the winner and report the change in score.
        if (googleStopwatch.Elapsed < microsoftStopwatch.Elapsed)
        {
            WinsByEndpoint.WithLabels(googleUrl).Inc(exemplar);
            LossesByEndpoint.WithLabels(microsoftUrl).Inc(exemplar);
        }
        else if (googleStopwatch.Elapsed > microsoftStopwatch.Elapsed)
        {
            WinsByEndpoint.WithLabels(microsoftUrl).Inc(exemplar);
            LossesByEndpoint.WithLabels(googleUrl).Inc(exemplar);
        }
        else
        {
            // It's a draw! No winner.
        }

        // Report the difference.
        var difference = Math.Abs(googleStopwatch.Elapsed.TotalSeconds - microsoftStopwatch.Elapsed.TotalSeconds);
        Difference.Observe(difference, exemplar: exemplar);

        // We finished one iteration of the service's work.
        IterationCount.Inc();
    }

    private static readonly Counter IterationCount = Metrics.CreateCounter("sampleservice_iterations_total", "Number of iterations that the sample service has ever executed.");

    private static readonly string[] ByEndpointLabelNames = new[] { "endpoint" };

    // We measure wins and losses.
    private static readonly Counter WinsByEndpoint = Metrics.CreateCounter("sampleservice_wins_total", "Number of times a target endpoint has won the competition.", ByEndpointLabelNames);
    private static readonly Counter LossesByEndpoint = Metrics.CreateCounter("sampleservice_losses_total", "Number of times a target endpoint has lost the competition.", ByEndpointLabelNames);

    // We measure a histogram of the absolute difference between the winner and loser.
    private static readonly Histogram Difference = Metrics.CreateHistogram("sampleservice_difference_seconds", "How far apart the winner and loser were, in seconds.", new HistogramConfiguration
    {
        // 0.01 seconds to 10 seconds range, by powers of ten.
        Buckets = Histogram.PowersOfTenDividedBuckets(-2, 1, 10)
    });
}
