// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Grpc.Net.Client;
using OpenTelemetry.Proto.Collector.Metrics.V1;
using OpenTelemetry.Proto.Common.V1;
using OpenTelemetry.Proto.Metrics.V1;
using OpenTelemetry.Proto.Resource.V1;

namespace Stress.TelemetryService;

/// <summary>
/// Send OTLP directly to the dashboard instead of going via opentelemetry-dotnet SDK to send raw and unlimited data.
/// </summary>
public class TelemetryStresser(ILogger<TelemetryStresser> logger, IConfiguration config) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        var address = config["OTEL_EXPORTER_OTLP_ENDPOINT"]!;
        var channel = GrpcChannel.ForAddress(address);

        var client = new MetricsService.MetricsServiceClient(channel);

        var value = 0;
        while (!cancellationToken.IsCancellationRequested)
        {
            value += Random.Shared.Next(0, 10);

            await ExportMetrics(logger, client, value, cancellationToken);

            await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
        }
    }

    private static async Task ExportMetrics(ILogger<TelemetryStresser> logger, MetricsService.MetricsServiceClient client, int value, CancellationToken cancellationToken)
    {
        var request = new ExportMetricsServiceRequest
        {
            ResourceMetrics =
                {
                    new ResourceMetrics
                    {
                        Resource = CreateResource("TestResource", "TestResource"),
                        ScopeMetrics =
                        {
                            new ScopeMetrics
                            {
                                Scope = new InstrumentationScope
                                {
                                    Name = "TestScope-<b>Bold</b>"
                                },
                                Metrics =
                                {
                                    CreateSumMetric("Test-<b>Bold</b>", DateTime.UtcNow, value: value)
                                }
                            }
                        }
                    }
                }
        };

        logger.LogDebug("Exporting metrics");
        var response = await client.ExportAsync(request, cancellationToken: cancellationToken);
        logger.LogDebug($"Export complete. Rejected count: {response.PartialSuccess?.RejectedDataPoints ?? 0}");
    }

    public static Resource CreateResource(string? name = null, string? instanceId = null)
    {
        return new Resource()
        {
            Attributes =
            {
                new KeyValue { Key = "service.name", Value = new AnyValue { StringValue = name ?? "TestService" } },
                new KeyValue { Key = "service.instance.id", Value = new AnyValue { StringValue = instanceId ?? "TestId" } }
            }
        };
    }

    public static Metric CreateSumMetric(string metricName, DateTime startTime, KeyValuePair<string, string>[]? attributes = null, int? value = null)
    {
        return new Metric
        {
            Name = metricName,
            Description = "Description-<b>Bold</b>",
            Unit = "Widget-<b>Bold</b>",
            Sum = new Sum
            {
                AggregationTemporality = AggregationTemporality.Cumulative,
                IsMonotonic = true,
                DataPoints =
                {
                    CreateNumberPoint(startTime, value ?? 1, attributes)
                }
            }
        };
    }

    private static NumberDataPoint CreateNumberPoint(DateTime startTime, int value, KeyValuePair<string, string>[]? attributes = null)
    {
        var point = new NumberDataPoint
        {
            AsInt = value,
            StartTimeUnixNano = DateTimeToUnixNanoseconds(startTime),
            TimeUnixNano = DateTimeToUnixNanoseconds(startTime)
        };
        if (attributes != null)
        {
            foreach (var attribute in attributes)
            {
                point.Attributes.Add(new KeyValue { Key = attribute.Key, Value = new AnyValue { StringValue = attribute.Value } });
            }
        }

        return point;
    }

    public static ulong DateTimeToUnixNanoseconds(DateTime dateTime)
    {
        var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var timeSinceEpoch = dateTime.ToUniversalTime() - unixEpoch;

        return (ulong)timeSinceEpoch.Ticks * 100;
    }
}
