// <copyright file="Program.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Net.Http;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using StackExchange.Redis;
using TestApplication.NServiceBus;
using TestApplication.Shared;

namespace TestApplication.CustomSdk;

public static class Program
{
    private static readonly ActivitySource ActivitySource = new(
        "TestApplication.CustomSdk");

    private static readonly Meter Meter = new("TestApplication.CustomSdk");
    private static readonly Counter<long> Counter = Meter.CreateCounter<long>("RequestCounter");

    public static async Task Main(string[] args)
    {
        ConsoleHelper.WriteSplashScreen(args);

        // When export of NServiceBus metrics is tested, which are updated on receive side,
        // test has to be marked as long running, in order to avoid random failures
        var longRunning = Environment.GetEnvironmentVariable("LONG_RUNNING") == "true";

        // if auto-instrumentation is not injecting sdk
        // then it's client's code responsibility
        // to subscribe to all activity sources

        var tracerProvider = BuildTracerProvider();
        var meterProvider = BuildMeterProvider();

        // When export of traces is tested, test is not marked as long running,
        // to avoid unnecessary delay due to default Redis instrumentation flush interval;
        // in this scenario it is required to add tracer/meter provider disposals on process exit
        if (!longRunning)
        {
            AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) =>
            {
                // autoinstrumentation disposes created instrumentations during AppDomain.CurrentDomain.ProcessExit
                // redis instrumentation flushes inside Dispose() which creates activities
                // delay providers disposal so that there are active listeners
                // when redis instrumentation attempts to create new activities
                tracerProvider?.Dispose();
                meterProvider?.Dispose();
            };
        }

        var endpointConfiguration = new EndpointConfiguration("TestApplication.NServiceBus");

        var learningTransport = new LearningTransport { StorageDirectory = Path.GetTempPath() };
        endpointConfiguration.UseTransport(learningTransport);

        using var cancellation = new CancellationTokenSource();
        var endpointInstance = await Endpoint.Start(endpointConfiguration, cancellation.Token);

        try
        {
            await endpointInstance.SendLocal(new TestMessage(), cancellation.Token);

            Counter.Add(1);

            using (var activity = ActivitySource.StartActivity("Manual"))
            {
                await PingRedis(args);

                using var client = new HttpClient();
                await client.GetStringAsync("https://www.bing.com", cancellation.Token);
            }

            // The "LONG_RUNNING" environment variable is used by tests that access/receive
            // data that takes time to be produced.
            if (longRunning)
            {
                // In this case it is necessary to ensure that the test has a chance to read the
                // expected data, only by keeping the application alive for some time that can
                // be ensured. Anyway, tests that set "LONG_RUNNING" env var to true are expected
                // to kill the process directly.
                Console.WriteLine("LONG_RUNNING is true, waiting for process to be killed...");
                await Process.GetCurrentProcess().WaitForExitAsync(cancellation.Token);
            }
        }
        finally
        {
            await endpointInstance.Stop(cancellation.Token);
        }
    }

    private static TracerProvider? BuildTracerProvider()
    {
        return Sdk
            .CreateTracerProviderBuilder()
            // bytecode instrumentation
            .AddSource("OpenTelemetry.Instrumentation.StackExchangeRedis")
            // lazily-loaded instrumentation
            .AddSource("OpenTelemetry.Instrumentation.Http.HttpClient")
            .AddSource("System.Net.Http") // This works only System.Net.Http >= 7.0.0
            .AddLegacySource("System.Net.Http.HttpRequestOut")
            // custom activity source
            .AddSource("TestApplication.CustomSdk")
            .AddSource("NServiceBus.Core")
            .ConfigureResource(builder =>
                builder.AddAttributes(new[] { new KeyValuePair<string, object>("test_attr", "added_manually") }))
            .AddOtlpExporter()
            .Build();
    }

    private static MeterProvider? BuildMeterProvider()
    {
        return Sdk
            .CreateMeterProviderBuilder()
            // lazily-loaded metric instrumentation
            .AddMeter("OpenTelemetry.Instrumentation.*")
            // bytecode metric instrumentation
            .AddMeter("NServiceBus.Core")
            // custom metric
            .AddMeter("TestApplication.CustomSdk")
            .ConfigureResource(builder =>
                builder.AddAttributes(new[] { new KeyValuePair<string, object>("test_attr", "added_manually") }))
            .AddOtlpExporter()
            .Build();
    }

    private static async Task PingRedis(string[] args)
    {
        var redisPort = GetRedisPort(args);

        var connectionString = $@"127.0.0.1:{redisPort}";

        using var connection = await ConnectionMultiplexer.ConnectAsync(connectionString);
        var db = connection.GetDatabase();

        db.Ping();
    }

    private static string GetRedisPort(string[] args)
    {
        if (args.Length > 1)
        {
            return args[1];
        }

        return "6379";
    }
}
