// <copyright file="TracerProviderSdkTest.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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenTelemetry.Instrumentation;
using OpenTelemetry.Resources;
using OpenTelemetry.Tests;
using Xunit;

namespace OpenTelemetry.Trace.Tests
{
    public class TracerProviderSdkTest : IDisposable
    {
        private const string ActivitySourceName = "TraceSdkTest";

        public TracerProviderSdkTest()
        {
            Activity.DefaultIdFormat = ActivityIdFormat.W3C;
        }

        [Fact]
        public void TracerProviderSdkAddSource()
        {
            using var source1 = new ActivitySource($"{Utils.GetCurrentMethodName()}.1");
            using var source2 = new ActivitySource($"{Utils.GetCurrentMethodName()}.2");

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(source1.Name)
                .Build();

            using (var activity = source1.StartActivity("test"))
            {
                Assert.NotNull(activity);
            }

            using (var activity = source2.StartActivity("test"))
            {
                Assert.Null(activity);
            }
        }

        [Fact]
        public void TracerProviderSdkAddSourceWithWildcards()
        {
            using var source1 = new ActivitySource($"{Utils.GetCurrentMethodName()}.A");
            using var source2 = new ActivitySource($"{Utils.GetCurrentMethodName()}.Ab");
            using var source3 = new ActivitySource($"{Utils.GetCurrentMethodName()}.Abc");
            using var source4 = new ActivitySource($"{Utils.GetCurrentMethodName()}.B");

            using (var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource($"{Utils.GetCurrentMethodName()}.*")
                .Build())
            {
                using (var activity = source1.StartActivity("test"))
                {
                    Assert.NotNull(activity);
                }

                using (var activity = source2.StartActivity("test"))
                {
                    Assert.NotNull(activity);
                }

                using (var activity = source3.StartActivity("test"))
                {
                    Assert.NotNull(activity);
                }

                using (var activity = source4.StartActivity("test"))
                {
                    Assert.NotNull(activity);
                }
            }

            using (var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource($"{Utils.GetCurrentMethodName()}.?")
                .Build())
            {
                using (var activity = source1.StartActivity("test"))
                {
                    Assert.NotNull(activity);
                }

                using (var activity = source2.StartActivity("test"))
                {
                    Assert.Null(activity);
                }

                using (var activity = source3.StartActivity("test"))
                {
                    Assert.Null(activity);
                }

                using (var activity = source4.StartActivity("test"))
                {
                    Assert.NotNull(activity);
                }
            }
        }

        [Fact]
        public void TracerProviderSdkInvokesSamplingWithCorrectParameters()
        {
            var testSampler = new TestSampler();
            using var activitySource = new ActivitySource(ActivitySourceName);
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .SetSampler(testSampler)
                .Build();

            // OpenTelemetry Sdk is expected to set default to W3C.
            Assert.True(Activity.DefaultIdFormat == ActivityIdFormat.W3C);

            using (var rootActivity = activitySource.StartActivity("root"))
            {
                Assert.NotNull(rootActivity);
                Assert.True(rootActivity.ParentSpanId == default);

                // Validate that the TraceId seen by Sampler is same as the
                // Activity when it got created.
                Assert.Equal(rootActivity.TraceId, testSampler.LatestSamplingParameters.TraceId);
                Assert.Null(testSampler.LatestSamplingParameters.Tags);
                Assert.Null(testSampler.LatestSamplingParameters.Links);
            }

            using (var parent = activitySource.StartActivity("parent", ActivityKind.Client))
            {
                Assert.Equal(parent.TraceId, testSampler.LatestSamplingParameters.TraceId);
                using var child = activitySource.StartActivity("child");
                Assert.Equal(child.TraceId, testSampler.LatestSamplingParameters.TraceId);
                Assert.Null(testSampler.LatestSamplingParameters.Tags);
                Assert.Null(testSampler.LatestSamplingParameters.Links);
                Assert.Equal(parent.TraceId, child.TraceId);
                Assert.Equal(parent.SpanId, child.ParentSpanId);
            }

            var customContext = new ActivityContext(
                ActivityTraceId.CreateRandom(),
                ActivitySpanId.CreateRandom(),
                ActivityTraceFlags.None);

            using (var fromCustomContext =
                activitySource.StartActivity("customContext", ActivityKind.Client, customContext))
            {
                Assert.Equal(fromCustomContext.TraceId, testSampler.LatestSamplingParameters.TraceId);
                Assert.Null(testSampler.LatestSamplingParameters.Tags);
                Assert.Null(testSampler.LatestSamplingParameters.Links);
                Assert.Equal(customContext.TraceId, fromCustomContext.TraceId);
                Assert.Equal(customContext.SpanId, fromCustomContext.ParentSpanId);
                Assert.NotEqual(customContext.SpanId, fromCustomContext.SpanId);
            }

            // Validate that Samplers get the tags passed with Activity creation
            var initialTags = new ActivityTagsCollection();
            initialTags["tagA"] = "tagAValue";
            using (var withInitialTags = activitySource.StartActivity("withInitialTags", ActivityKind.Client, default(ActivityContext), initialTags))
            {
                Assert.Equal(withInitialTags.TraceId, testSampler.LatestSamplingParameters.TraceId);
                Assert.Equal(initialTags, testSampler.LatestSamplingParameters.Tags);
            }

            // Validate that Samplers get the links passed with Activity creation
            var links = new List<ActivityLink>();
            var linkContext1 = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded);
            var linkContext2 = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded);
            var link1 = new ActivityLink(linkContext1);
            var link2 = new ActivityLink(linkContext2);
            links.Add(link1);
            links.Add(link2);

            using (var withInitialTags = activitySource.StartActivity("withLinks", ActivityKind.Client, default(ActivityContext), links: links))
            {
                Assert.Equal(withInitialTags.TraceId, testSampler.LatestSamplingParameters.TraceId);
                Assert.Null(testSampler.LatestSamplingParameters.Tags);
                Assert.Equal(links, testSampler.LatestSamplingParameters.Links);
            }

            // Validate that when StartActivity is called using Parent as string,
            // Sampling is called correctly.
            var act = new Activity("anything").Start();
            act.Stop();
            var customContextAsString = act.Id;
            var expectedTraceId = act.TraceId;
            var expectedParentSpanId = act.SpanId;

            using (var fromCustomContextAsString =
                activitySource.StartActivity("customContext", ActivityKind.Client, customContextAsString))
            {
                Assert.Equal(fromCustomContextAsString.TraceId, testSampler.LatestSamplingParameters.TraceId);
                Assert.Equal(expectedTraceId, fromCustomContextAsString.TraceId);
                Assert.Equal(expectedParentSpanId, fromCustomContextAsString.ParentSpanId);
            }

            // Verify that StartActivity returns an instance of Activity.
            using var fromInvalidW3CIdParent =
                activitySource.StartActivity("customContext", ActivityKind.Client, "InvalidW3CIdParent");
            Assert.NotNull(fromInvalidW3CIdParent);

            // Verify that the TestSampler was invoked and received the correct params.
            Assert.Equal(fromInvalidW3CIdParent.TraceId, testSampler.LatestSamplingParameters.TraceId);

            // OpenTelemetry ActivityContext does not support non W3C Ids.
            Assert.Null(fromInvalidW3CIdParent.ParentId);
            Assert.Equal(default, fromInvalidW3CIdParent.ParentSpanId);
        }

        [Theory]
        [InlineData(SamplingDecision.Drop)]
        [InlineData(SamplingDecision.RecordOnly)]
        [InlineData(SamplingDecision.RecordAndSample)]
        public void TracerProviderSdkSamplerAttributesAreAppliedToActivity(SamplingDecision sampling)
        {
            var testSampler = new TestSampler
            {
                SamplingAction = (samplingParams) =>
                {
                    var attributes = new Dictionary<string, object>
                    {
                        { "tagkeybysampler", "tagvalueaddedbysampler" },
                    };
                    return new SamplingResult(sampling, attributes);
                },
            };

            using var activitySource = new ActivitySource(ActivitySourceName);
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .SetSampler(testSampler)
                .Build();

            using var rootActivity = activitySource.StartActivity("root");
            Assert.NotNull(rootActivity);
            Assert.Equal(rootActivity.TraceId, testSampler.LatestSamplingParameters.TraceId);
            if (sampling != SamplingDecision.Drop)
            {
                Assert.Contains(new KeyValuePair<string, object>("tagkeybysampler", "tagvalueaddedbysampler"), rootActivity.TagObjects);
            }
        }

        [Fact]
        public void TracerSdkSetsActivitySamplingResultAsPropagationWhenParentIsRemote()
        {
            var testSampler = new TestSampler();
            using var activitySource = new ActivitySource(ActivitySourceName);
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                    .AddSource(ActivitySourceName)
                    .SetSampler(testSampler)
                    .Build();

            testSampler.SamplingAction = (samplingParameters) =>
            {
                return new SamplingResult(SamplingDecision.Drop);
            };

            ActivityContext ctx = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, isRemote: true);

            using (var activity = activitySource.StartActivity("root", ActivityKind.Server, ctx))
            {
                // Even if sampling returns false, for activities with remote parent,
                // activity is still created with PropagationOnly.
                Assert.NotNull(activity);
                Assert.False(activity.IsAllDataRequested);
                Assert.False(activity.Recorded);

                // This is not a root activity and parent is not remote.
                // If sampling returns false, no activity is created at all.
                using var innerActivity = activitySource.StartActivity("inner");
                Assert.Null(innerActivity);
            }
        }

        [Fact]
        public void TracerSdkSetsActivitySamplingResultBasedOnSamplingDecision()
        {
            var testSampler = new TestSampler();
            using var activitySource = new ActivitySource(ActivitySourceName);
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                    .AddSource(ActivitySourceName)
                    .SetSampler(testSampler)
                    .Build();

            testSampler.SamplingAction = (samplingParameters) =>
            {
                return new SamplingResult(SamplingDecision.RecordAndSample);
            };

            using (var activity = activitySource.StartActivity("root"))
            {
                Assert.NotNull(activity);
                Assert.True(activity.IsAllDataRequested);
                Assert.True(activity.Recorded);
            }

            testSampler.SamplingAction = (samplingParameters) =>
            {
                return new SamplingResult(SamplingDecision.RecordOnly);
            };

            using (var activity = activitySource.StartActivity("root"))
            {
                // Even if sampling returns false, for root activities,
                // activity is still created with PropagationOnly.
                Assert.NotNull(activity);
                Assert.True(activity.IsAllDataRequested);
                Assert.False(activity.Recorded);
            }

            testSampler.SamplingAction = (samplingParameters) =>
            {
                return new SamplingResult(SamplingDecision.Drop);
            };

            using (var activity = activitySource.StartActivity("root"))
            {
                // Even if sampling returns false, for root activities,
                // activity is still created with PropagationOnly.
                Assert.NotNull(activity);
                Assert.False(activity.IsAllDataRequested);
                Assert.False(activity.Recorded);

                // This is not a root activity.
                // If sampling returns false, no activity is created at all.
                using var innerActivity = activitySource.StartActivity("inner");
                Assert.Null(innerActivity);
            }
        }

        [Fact]
        public void TracerSdkSetsActivitySamplingResultToNoneWhenSuppressInstrumentationIsTrue()
        {
            using var scope = SuppressInstrumentationScope.Begin();

            var testSampler = new TestSampler();
            using var activitySource = new ActivitySource(ActivitySourceName);
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                    .AddSource(ActivitySourceName)
                    .SetSampler(testSampler)
                    .Build();

            using var activity = activitySource.StartActivity("root");
            Assert.Null(activity);
        }

        [Fact]
        public void TracerSdkSetsActivityDataRequestedToFalseWhenSuppressInstrumentationIsTrueForLegacyActivity()
        {
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    endCalled = true;
                };

            using var openTelemetry = Sdk.CreateTracerProviderBuilder()
                        .AddLegacySource("random")
                        .AddProcessor(testActivityProcessor)
                        .SetSampler(new AlwaysOnSampler())
                        .Build();

            using (SuppressInstrumentationScope.Begin(true))
            {
                using var activity = new Activity("random").Start();
                Assert.False(activity.IsAllDataRequested);
            }

            Assert.False(startCalled);
            Assert.False(endCalled);
        }

        [Fact]
        public void ProcessorDoesNotReceiveNotRecordDecisionSpan()
        {
            var testSampler = new TestSampler();
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    endCalled = true;
                };

            using var openTelemetry = Sdk.CreateTracerProviderBuilder()
                        .AddSource("random")
                        .AddProcessor(testActivityProcessor)
                        .SetSampler(testSampler)
                        .Build();

            testSampler.SamplingAction = (samplingParameters) =>
            {
                return new SamplingResult(SamplingDecision.Drop);
            };

            using ActivitySource source = new ActivitySource("random");
            var activity = source.StartActivity("somename");
            activity.Stop();

            Assert.False(activity.IsAllDataRequested);
            Assert.Equal(ActivityTraceFlags.None, activity.ActivityTraceFlags);
            Assert.False(activity.Recorded);
            Assert.False(startCalled);
            Assert.False(endCalled);
        }

        // Test to check that TracerProvider does not call Processor.OnStart or Processor.OnEnd for a legacy activity when no legacy OperationName is
        // provided to TracerProviderBuilder.
        [Fact]
        public void SdkDoesNotProcessLegacyActivityWithNoAdditionalConfig()
        {
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalled = true;
                };

            var emptyActivitySource = new ActivitySource(string.Empty);
            Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet

            // No AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .AddProcessor(testActivityProcessor)
                        .Build();

            Assert.False(emptyActivitySource.HasListeners()); // No listener for empty ActivitySource even after build

            Activity activity = new Activity("Test");
            activity.Start();
            activity.Stop();

            Assert.False(startCalled); // Processor.OnStart is not called since we did not add any legacy OperationName
            Assert.False(endCalled); // Processor.OnEnd is not called since we did not add any legacy OperationName
        }

        // Test to check that TracerProvider samples a legacy activity using a custom Sampler and calls Processor.OnStart and Processor.OnEnd for the
        // legacy activity when the correct legacy OperationName is provided to TracerProviderBuilder.
        [Fact]
        public void SdkSamplesAndProcessesLegacyActivityWithRightConfig()
        {
            bool samplerCalled = false;

            var sampler = new TestSampler
            {
                SamplingAction =
                (samplingParameters) =>
                {
                    samplerCalled = true;
                    return new SamplingResult(SamplingDecision.RecordAndSample);
                },
            };

            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.True(samplerCalled);
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalled = true;
                };

            var emptyActivitySource = new ActivitySource(string.Empty);
            Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet

            var operationNameForLegacyActivity = "TestOperationName";

            // AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddProcessor(testActivityProcessor)
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            activity.Stop();

            Assert.True(startCalled); // Processor.OnStart is called since we added a legacy OperationName
            Assert.True(endCalled); // Processor.OnEnd is called since we added a legacy OperationName
        }

        // Test to check that TracerProvider samples a legacy activity using a custom Sampler and calls Processor.OnStart and Processor.OnEnd for the
        // legacy activity when the correct legacy OperationName is provided to TracerProviderBuilder and a wildcard Source is added
        [Fact]
        public void SdkSamplesAndProcessesLegacyActivityWithRightConfigOnWildCardMode()
        {
            bool samplerCalled = false;

            var sampler = new TestSampler
            {
                SamplingAction =
                (samplingParameters) =>
                {
                    samplerCalled = true;
                    return new SamplingResult(SamplingDecision.RecordAndSample);
                },
            };

            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.True(samplerCalled);
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalled = true;
                };

            var emptyActivitySource = new ActivitySource(string.Empty);
            Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet

            var operationNameForLegacyActivity = "TestOperationName";

            // AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddSource("ABCCompany.XYZProduct.*") // Adding a wild card source
                        .AddProcessor(testActivityProcessor)
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            activity.Stop();

            Assert.True(startCalled); // Processor.OnStart is called since we added a legacy OperationName
            Assert.True(endCalled); // Processor.OnEnd is called since we added a legacy OperationName
        }

        // Test to check that TracerProvider does not call Processor.OnEnd for a legacy activity whose ActivitySource got updated before Activity.Stop and
        // the updated source was not added to the Provider
        [Fact]
        public void SdkCallsOnlyProcessorOnStartForLegacyActivityWhenActivitySourceIsUpdatedWithoutAddSource()
        {
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalled = true;
                };

            var emptyActivitySource = new ActivitySource(string.Empty);
            Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet

            var operationNameForLegacyActivity = "TestOperationName";
            var activitySourceForLegacyActivity = new ActivitySource("TestActivitySource", "1.0.0");

            // AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .AddLegacySource(operationNameForLegacyActivity)
                        .AddProcessor(testActivityProcessor)
                        .Build();

            Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            ActivityInstrumentationHelper.SetActivitySourceProperty(activity, activitySourceForLegacyActivity);
            activity.Stop();

            Assert.True(startCalled); // Processor.OnStart is called since we provided the legacy OperationName
            Assert.False(endCalled); // Processor.OnEnd is not called since the ActivitySource is updated and the updated source name is not added as a Source to the provider
        }

        // Test to check that TracerProvider calls Processor.OnStart and Processor.OnEnd for a legacy activity whose ActivitySource got updated before Activity.Stop and
        // the updated source was added to the Provider
        [Fact]
        public void SdkProcessesLegacyActivityWhenActivitySourceIsUpdatedWithAddSource()
        {
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalled = true;
                };

            var emptyActivitySource = new ActivitySource(string.Empty);
            Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet

            var operationNameForLegacyActivity = "TestOperationName";
            var activitySourceForLegacyActivity = new ActivitySource("TestActivitySource", "1.0.0");

            // AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .AddSource(activitySourceForLegacyActivity.Name) // Add the updated ActivitySource as a Source
                        .AddLegacySource(operationNameForLegacyActivity)
                        .AddProcessor(testActivityProcessor)
                        .Build();

            Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            ActivityInstrumentationHelper.SetActivitySourceProperty(activity, activitySourceForLegacyActivity);
            activity.Stop();

            Assert.True(startCalled); // Processor.OnStart is called since we provided the legacy OperationName
            Assert.True(endCalled); // Processor.OnEnd is not called since the ActivitySource is updated and the updated source name is added as a Source to the provider
        }

        // Test to check that TracerProvider continues to process legacy activities even after a new Processor is added after the building the provider.
        [Fact]
        public void SdkProcessesLegacyActivityEvenAfterAddingNewProcessor()
        {
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            bool startCalled = false;
            bool endCalled = false;

            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalled = true;
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalled = true;
                };

            var operationNameForLegacyActivity = "TestOperationName";

            // AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddProcessor(testActivityProcessor)
                .AddLegacySource(operationNameForLegacyActivity)
                .Build();

            Assert.Equal(tracerProvider, testActivityProcessor.ParentProvider);

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            activity.Stop();

            Assert.True(startCalled);
            Assert.True(endCalled);

            // As Processors can be added anytime after Provider construction, the following validates
            // the following validates that updated processors are processing the legacy activities created from here on.
            TestActivityProcessor testActivityProcessorNew = new TestActivityProcessor();

            bool startCalledNew = false;
            bool endCalledNew = false;

            testActivityProcessorNew.StartAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    startCalledNew = true;
                };

            testActivityProcessorNew.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    endCalledNew = true;
                };

            tracerProvider.AddProcessor(testActivityProcessorNew);

            var sdkProvider = (TracerProviderSdk)tracerProvider;

            Assert.True(sdkProvider.Processor is CompositeProcessor<Activity>);
            Assert.Equal(tracerProvider, sdkProvider.Processor.ParentProvider);
            Assert.Equal(tracerProvider, testActivityProcessorNew.ParentProvider);

            Activity activityNew = new Activity(operationNameForLegacyActivity); // Create a new Activity with the same operation name
            activityNew.Start();
            activityNew.Stop();

            Assert.True(startCalledNew);
            Assert.True(endCalledNew);
        }

        [Fact]
        public void SdkSamplesLegacyActivityWithAlwaysOnSampler()
        {
            var operationNameForLegacyActivity = "TestOperationName";
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(new AlwaysOnSampler())
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();

            Assert.True(activity.IsAllDataRequested);
            Assert.True(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));

            // Validating ActivityTraceFlags is not enough as it does not get reflected on
            // Id, If the Id is accessed before the sampler runs.
            // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700
            Assert.EndsWith("-01", activity.Id);

            activity.Stop();
        }

        [Fact]
        public void SdkSamplesLegacyActivityWithAlwaysOffSampler()
        {
            var operationNameForLegacyActivity = "TestOperationName";
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(new AlwaysOffSampler())
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();

            Assert.False(activity.IsAllDataRequested);
            Assert.False(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));

            // Validating ActivityTraceFlags is not enough as it does not get reflected on
            // Id, If the Id is accessed before the sampler runs.
            // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700
            Assert.EndsWith("-00", activity.Id);

            activity.Stop();
        }

        [Theory]
        [InlineData(SamplingDecision.Drop, false, false)]
        [InlineData(SamplingDecision.RecordOnly, true, false)]
        [InlineData(SamplingDecision.RecordAndSample, true, true)]
        public void SdkSamplesLegacyActivityWithCustomSampler(SamplingDecision samplingDecision, bool isAllDataRequested, bool hasRecordedFlag)
        {
            var operationNameForLegacyActivity = "TestOperationName";
            var sampler = new TestSampler() { SamplingAction = (samplingParameters) => new SamplingResult(samplingDecision) };

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();

            Assert.Equal(isAllDataRequested, activity.IsAllDataRequested);
            Assert.Equal(hasRecordedFlag, activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));

            // Validating ActivityTraceFlags is not enough as it does not get reflected on
            // Id, If the Id is accessed before the sampler runs.
            // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700
            Assert.EndsWith(hasRecordedFlag ? "-01" : "-00", activity.Id);

            activity.Stop();
        }

        [Fact]
        public void SdkPopulatesSamplingParamsCorrectlyForRootLegacyActivity()
        {
            var operationNameForLegacyActivity = "TestOperationName";
            var sampler = new TestSampler()
            {
                SamplingAction = (samplingParameters) =>
                {
                    Assert.Equal(default, samplingParameters.ParentContext);
                    return new SamplingResult(SamplingDecision.RecordAndSample);
                },
            };

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            // Start activity without setting parent. i.e it'll have null parent
            // and becomes root activity
            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            activity.Stop();
        }

        [Theory]
        [InlineData(SamplingDecision.Drop, ActivityTraceFlags.None, false, false)]
        [InlineData(SamplingDecision.Drop, ActivityTraceFlags.Recorded, false, false)]
        [InlineData(SamplingDecision.RecordOnly, ActivityTraceFlags.None, true, false)]
        [InlineData(SamplingDecision.RecordOnly, ActivityTraceFlags.Recorded, true, false)]
        [InlineData(SamplingDecision.RecordAndSample, ActivityTraceFlags.None, true, true)]
        [InlineData(SamplingDecision.RecordAndSample, ActivityTraceFlags.Recorded, true, true)]
        public void SdkSamplesLegacyActivityWithRemoteParentWithCustomSampler(SamplingDecision samplingDecision, ActivityTraceFlags parentTraceFlags, bool expectedIsAllDataRequested, bool hasRecordedFlag)
        {
            var parentTraceId = ActivityTraceId.CreateRandom();
            var parentSpanId = ActivitySpanId.CreateRandom();
            var parentTraceFlag = (parentTraceFlags == ActivityTraceFlags.Recorded) ? "01" : "00";
            string remoteParentId = $"00-{parentTraceId}-{parentSpanId}-{parentTraceFlag}";
            string tracestate = "a=b;c=d";

            var operationNameForLegacyActivity = "TestOperationName";
            var sampler = new TestSampler()
            {
                SamplingAction = (samplingParameters) =>
                {
                    // Ensure that SDK populates the sampling parameters correctly
                    Assert.Equal(parentTraceId, samplingParameters.ParentContext.TraceId);
                    Assert.Equal(parentSpanId, samplingParameters.ParentContext.SpanId);
                    Assert.Equal(parentTraceFlags, samplingParameters.ParentContext.TraceFlags);
                    Assert.Equal(tracestate, samplingParameters.ParentContext.TraceState);
                    return new SamplingResult(samplingDecision);
                },
            };

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            // Create an activity with remote parent id.
            // The sampling parameters are expected to be that of the
            // parent context i.e the remote parent.

            Activity activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId);
            activity.TraceStateString = tracestate;

            // At this point SetParentId has set the ActivityTraceFlags to that of the parent activity. The activity is now passed to the sampler.
            activity.Start();
            Assert.Equal(expectedIsAllDataRequested, activity.IsAllDataRequested);
            Assert.Equal(hasRecordedFlag, activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));

            // Validating ActivityTraceFlags is not enough as it does not get reflected on
            // Id, If the Id is accessed before the sampler runs.
            // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700
            Assert.EndsWith(hasRecordedFlag ? "-01" : "-00", activity.Id);
            activity.Stop();
        }

        [Theory]
        [InlineData(ActivityTraceFlags.None)]
        [InlineData(ActivityTraceFlags.Recorded)]
        public void SdkSamplesLegacyActivityWithRemoteParentWithAlwaysOnSampler(ActivityTraceFlags parentTraceFlags)
        {
            var parentTraceId = ActivityTraceId.CreateRandom();
            var parentSpanId = ActivitySpanId.CreateRandom();
            var parentTraceFlag = (parentTraceFlags == ActivityTraceFlags.Recorded) ? "01" : "00";
            string remoteParentId = $"00-{parentTraceId}-{parentSpanId}-{parentTraceFlag}";

            var operationNameForLegacyActivity = "TestOperationName";

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(new AlwaysOnSampler())
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            // Create an activity with remote parent id.
            // The sampling parameters are expected to be that of the
            // parent context i.e the remote parent.

            Activity activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId);

            // At this point SetParentId has set the ActivityTraceFlags to that of the parent activity. The activity is now passed to the sampler.
            activity.Start();
            Assert.True(activity.IsAllDataRequested);
            Assert.True(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));

            // Validating ActivityTraceFlags is not enough as it does not get reflected on
            // Id, If the Id is accessed before the sampler runs.
            // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700
            Assert.EndsWith("-01", activity.Id);
            activity.Stop();
        }

        [Theory]
        [InlineData(ActivityTraceFlags.None)]
        [InlineData(ActivityTraceFlags.Recorded)]
        public void SdkSamplesLegacyActivityWithRemoteParentWithAlwaysOffSampler(ActivityTraceFlags parentTraceFlags)
        {
            var parentTraceId = ActivityTraceId.CreateRandom();
            var parentSpanId = ActivitySpanId.CreateRandom();
            var parentTraceFlag = (parentTraceFlags == ActivityTraceFlags.Recorded) ? "01" : "00";
            string remoteParentId = $"00-{parentTraceId}-{parentSpanId}-{parentTraceFlag}";

            var operationNameForLegacyActivity = "TestOperationName";

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(new AlwaysOffSampler())
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            // Create an activity with remote parent id.
            // The sampling parameters are expected to be that of the
            // parent context i.e the remote parent.

            Activity activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId);

            // At this point SetParentId has set the ActivityTraceFlags to that of the parent activity. The activity is now passed to the sampler.
            activity.Start();
            Assert.False(activity.IsAllDataRequested);
            Assert.False(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));

            // Validating ActivityTraceFlags is not enough as it does not get reflected on
            // Id, If the Id is accessed before the sampler runs.
            // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700
            Assert.EndsWith("-00", activity.Id);
            activity.Stop();
        }

        [Theory]
        [InlineData(ActivityTraceFlags.None)]
        [InlineData(ActivityTraceFlags.Recorded)]
        public void SdkPopulatesSamplingParamsCorrectlyForLegacyActivityWithInProcParent(ActivityTraceFlags traceFlags)
        {
            // Create some parent activity.
            string tracestate = "a=b;c=d";
            var activityLocalParent = new Activity("TestParent")
            {
                ActivityTraceFlags = traceFlags,
                TraceStateString = tracestate,
            };
            activityLocalParent.Start();

            var operationNameForLegacyActivity = "TestOperationName";
            var sampler = new TestSampler()
            {
                SamplingAction = (samplingParameters) =>
                {
                    Assert.Equal(activityLocalParent.TraceId, samplingParameters.ParentContext.TraceId);
                    Assert.Equal(activityLocalParent.SpanId, samplingParameters.ParentContext.SpanId);
                    Assert.Equal(activityLocalParent.ActivityTraceFlags, samplingParameters.ParentContext.TraceFlags);
                    Assert.Equal(tracestate, samplingParameters.ParentContext.TraceState);
                    return new SamplingResult(SamplingDecision.RecordAndSample);
                },
            };

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddLegacySource(operationNameForLegacyActivity)
                        .Build();

            // This activity will have a inproc parent.
            // activity.Parent will be equal to the activity created at the beginning of this test.
            // Sampling parameters are expected to be that of the parentContext.
            // i.e of the parent Activity
            Activity activity = new Activity(operationNameForLegacyActivity);
            activity.Start();
            activity.Stop();
        }

        [Fact]
        public void TracerProvideSdkCreatesAndDiposesInstrumentation()
        {
            TestInstrumentation testInstrumentation = null;
            var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .AddInstrumentation(() =>
                        {
                            testInstrumentation = new TestInstrumentation();
                            return testInstrumentation;
                        })
                        .Build();

            Assert.NotNull(testInstrumentation);
            Assert.False(testInstrumentation.IsDisposed);
            tracerProvider.Dispose();
            Assert.True(testInstrumentation.IsDisposed);
        }

        [Fact]
        public void TracerProviderSdkBuildsWithDefaultResource()
        {
            var tracerProvider = Sdk.CreateTracerProviderBuilder().Build();
            var resource = tracerProvider.GetResource();

            Assert.NotNull(resource);
            Assert.NotEqual(Resource.Empty, resource);
            Assert.Single(resource.Attributes);
            Assert.Equal(ResourceSemanticConventions.AttributeServiceName, resource.Attributes.FirstOrDefault().Key);
            Assert.Contains("unknown_service", (string)resource.Attributes.FirstOrDefault().Value);
        }

        [Theory]
        [InlineData(null)]
        [InlineData("")]
        [InlineData(" ")]
        public void AddLegacyOperationName_BadArgs(string operationName)
        {
            var builder = Sdk.CreateTracerProviderBuilder();
            Assert.Throws<ArgumentException>(() => builder.AddLegacySource(operationName));
        }

        [Fact]
        public void AddLegacyOperationNameAddsActivityListenerForEmptyActivitySource()
        {
            var emptyActivitySource = new ActivitySource(string.Empty);
            var builder = Sdk.CreateTracerProviderBuilder();
            builder.AddLegacySource("TestOperationName");

            Assert.False(emptyActivitySource.HasListeners());
            using var provider = builder.Build();
            Assert.True(emptyActivitySource.HasListeners());
        }

        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public void TracerProviderSdkBuildsWithSDKResource(bool useConfigure)
        {
            var tracerProvider = useConfigure ?
                Sdk.CreateTracerProviderBuilder().SetResourceBuilder(
                    ResourceBuilder.CreateDefault().AddTelemetrySdk()).Build() :
                Sdk.CreateTracerProviderBuilder().ConfigureResource(r => r.AddTelemetrySdk()).Build();
            var resource = tracerProvider.GetResource();
            var attributes = resource.Attributes;

            Assert.NotNull(resource);
            Assert.NotEqual(Resource.Empty, resource);
            Assert.Contains(new KeyValuePair<string, object>("telemetry.sdk.name", "opentelemetry"), attributes);
            Assert.Contains(new KeyValuePair<string, object>("telemetry.sdk.language", "dotnet"), attributes);
            var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version"));
            Assert.Single(versionAttribute);
        }

        [Fact]
        public void TracerProviderSdkFlushesProcessorForcibly()
        {
            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .AddProcessor(testActivityProcessor)
                        .Build();

            var isFlushed = tracerProvider.ForceFlush();

            Assert.True(isFlushed);
            Assert.True(testActivityProcessor.ForceFlushCalled);
        }

        [Fact]
        public void SdkSamplesAndProcessesLegacySourceWhenAddLegacySourceIsCalledWithWildcardValue()
        {
            var sampledActivities = new List<string>();
            var sampler = new TestSampler
            {
                SamplingAction =
                (samplingParameters) =>
                {
                    sampledActivities.Add(samplingParameters.Name);
                    return new SamplingResult(SamplingDecision.RecordAndSample);
                },
            };

            using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();

            var onStartProcessedActivities = new List<string>();
            var onStopProcessedActivities = new List<string>();
            testActivityProcessor.StartAction =
                (a) =>
                {
                    Assert.Contains(a.OperationName, sampledActivities);
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true
                    onStartProcessedActivities.Add(a.OperationName);
                };

            testActivityProcessor.EndAction =
                (a) =>
                {
                    Assert.False(Sdk.SuppressInstrumentation);
                    Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
                    onStopProcessedActivities.Add(a.OperationName);
                };

            var legacySourceNamespaces = new[] { "LegacyNamespace.*", "Namespace.*.Operation" };
            using var activitySource = new ActivitySource(ActivitySourceName);

            // AddLegacyOperationName chained to TracerProviderBuilder
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                        .SetSampler(sampler)
                        .AddProcessor(testActivityProcessor)
                        .AddLegacySource(legacySourceNamespaces[0])
                        .AddLegacySource(legacySourceNamespaces[1])
                        .AddSource(ActivitySourceName)
                        .Build();

            foreach (var ns in legacySourceNamespaces)
            {
                var startOpName = ns.Replace("*", "Start");
                Activity startOperation = new Activity(startOpName);
                startOperation.Start();
                startOperation.Stop();

                Assert.Contains(startOpName, onStartProcessedActivities); // Processor.OnStart is called since we added a legacy OperationName
                Assert.Contains(startOpName, onStopProcessedActivities);  // Processor.OnEnd is called since we added a legacy OperationName

                var stopOpName = ns.Replace("*", "Stop");
                Activity stopOperation = new Activity(stopOpName);
                stopOperation.Start();
                stopOperation.Stop();

                Assert.Contains(stopOpName, onStartProcessedActivities); // Processor.OnStart is called since we added a legacy OperationName
                Assert.Contains(stopOpName, onStopProcessedActivities);  // Processor.OnEnd is called since we added a legacy OperationName
            }

            // Ensure we can still process "normal" activities when in legacy wildcard mode.
            Activity nonLegacyActivity = activitySource.StartActivity("TestActivity");
            nonLegacyActivity.Start();
            nonLegacyActivity.Stop();

            Assert.Contains(nonLegacyActivity.OperationName, onStartProcessedActivities); // Processor.OnStart is called since we added a legacy OperationName
            Assert.Contains(nonLegacyActivity.OperationName, onStopProcessedActivities);  // Processor.OnEnd is called since we added a legacy OperationName
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }

        private class TestInstrumentation : IDisposable
        {
            public bool IsDisposed;

            public TestInstrumentation()
            {
                this.IsDisposed = false;
            }

            public void Dispose()
            {
                this.IsDisposed = true;
            }
        }
    }
}
