// <copyright file="ActivityExtensionsTest.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.Diagnostics;
using System.Linq;
using Xunit;

namespace OpenTelemetry.Trace.Tests
{
    public class ActivityExtensionsTest
    {
        private const string ActivitySourceName = "test.status";
        private const string ActivityName = "Test Activity";

        [Fact]
        public void SetStatus()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity.SetStatus(Status.Ok);
            activity?.Stop();

            Assert.Equal(Status.Ok, activity.GetStatus());
        }

        [Fact]
        public void SetStatusWithDescription()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity.SetStatus(Status.Error.WithDescription("Not Found"));
            activity?.Stop();

            var status = activity.GetStatus();
            Assert.Equal(StatusCode.Error, status.StatusCode);
            Assert.Equal("Not Found", status.Description);
        }

        [Fact]
        public void SetStatusWithDescriptionTwice()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity.SetStatus(Status.Error.WithDescription("Not Found"));
            activity.SetStatus(Status.Ok);
            activity?.Stop();

            var status = activity.GetStatus();
            Assert.Equal(StatusCode.Ok, status.StatusCode);
            Assert.Null(status.Description);
        }

        [Fact]
        public void SetStatusWithIgnoredDescription()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity.SetStatus(Status.Ok.WithDescription("This should be ignored."));
            activity?.Stop();

            var status = activity.GetStatus();
            Assert.Equal(StatusCode.Ok, status.StatusCode);
            Assert.Null(status.Description);
        }

        [Fact]
        public void SetCancelledStatus()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity.SetStatus(Status.Error);
            activity?.Stop();

            Assert.True(activity.GetStatus().StatusCode.Equals(Status.Error.StatusCode));
        }

        [Fact]
        public void GetStatusWithNoStatusInActivity()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity?.Stop();

            Assert.Equal(Status.Unset, activity.GetStatus());
        }

        [Fact]
        public void LastSetStatusWins()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource(ActivitySourceName)
                .Build();

            using var source = new ActivitySource(ActivitySourceName);
            using var activity = source.StartActivity(ActivityName);
            activity.SetStatus(Status.Error);
            activity.SetStatus(Status.Ok);
            activity?.Stop();

            Assert.Equal(Status.Ok, activity.GetStatus());
        }

        [Fact]
        public void CheckRecordException()
        {
            var message = "message";
            var exception = new ArgumentNullException(message, new Exception(message));
            var activity = new Activity("test-activity");
            activity.RecordException(exception);

            var @event = activity.Events.FirstOrDefault(e => e.Name == SemanticConventions.AttributeExceptionEventName);
            Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
            Assert.Equal("System.ArgumentNullException", @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
        }

        [Fact]
        public void RecordExceptionWithAdditionalTags()
        {
            var message = "message";
            var exception = new ArgumentNullException(message, new Exception(message));
            var activity = new Activity("test-activity");

            var tags = new TagList
            {
                { "key1", "value1" },
                { "key2", "value2" },
            };

            activity.RecordException(exception, tags);

            // Additional tags passed in override attributes added from the exception
            tags.Add(SemanticConventions.AttributeExceptionMessage, "SomeOtherExceptionMessage");
            tags.Add(SemanticConventions.AttributeExceptionType, "SomeOtherExceptionType");

            activity.RecordException(exception, tags);

            var events = activity.Events.ToArray();
            Assert.Equal(2, events.Length);

            Assert.Equal(SemanticConventions.AttributeExceptionEventName, events[0].Name);
            Assert.Equal(message, events[0].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
            Assert.Equal("System.ArgumentNullException", events[0].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
            Assert.Equal("value1", events[0].Tags.First(t => t.Key == "key1").Value);
            Assert.Equal("value2", events[0].Tags.First(t => t.Key == "key2").Value);

            Assert.Equal(SemanticConventions.AttributeExceptionEventName, events[1].Name);
            Assert.Equal("SomeOtherExceptionMessage", events[1].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
            Assert.Equal("SomeOtherExceptionType", events[1].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
            Assert.Equal("value1", events[1].Tags.First(t => t.Key == "key1").Value);
            Assert.Equal("value2", events[1].Tags.First(t => t.Key == "key2").Value);
        }

        [Fact]
        public void GetTagValueEmpty()
        {
            Activity activity = new Activity("Test");

            Assert.Null(activity.GetTagValue("Tag1"));
        }

        [Fact]
        public void GetTagValue()
        {
            Activity activity = new Activity("Test");
            activity.SetTag("Tag1", "Value1");

            Assert.Equal("Value1", activity.GetTagValue("Tag1"));
            Assert.Null(activity.GetTagValue("tag1"));
            Assert.Null(activity.GetTagValue("Tag2"));
        }

        [Theory]
        [InlineData("Key", "Value", true)]
        [InlineData("CustomTag", null, false)]
        public void TryCheckFirstTag(string tagName, object expectedTagValue, bool expectedResult)
        {
            Activity activity = new Activity("Test");
            activity.SetTag("Key", "Value");

            var result = activity.TryCheckFirstTag(tagName, out var tagValue);
            Assert.Equal(expectedResult, result);
            Assert.Equal(expectedTagValue, tagValue);
        }

        [Fact]
        public void TryCheckFirstTagReturnsFalseForActivityWithNoTags()
        {
            Activity activity = new Activity("Test");

            var result = activity.TryCheckFirstTag("Key", out var tagValue);
            Assert.False(result);
            Assert.Null(tagValue);
        }
    }
}
