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

using System;
using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Xunit;
using Xunit.NetCore.Extensions;

#nullable disable

namespace Microsoft.Build.UnitTests.Logging
{
    /// <summary>
    /// Test the central forwarding logger by initializing a new one and sending events through it.
    /// </summary>
    public class BuildEventArgTransportSink_Tests
    {
        /// <summary>
        /// Verify the properties on BuildEventArgTransportSink properly work
        /// </summary>
        [Fact]
        public void PropertyTests()
        {
            BuildEventArgTransportSink sink = new BuildEventArgTransportSink(PacketProcessor);
            Assert.Null(sink.Name);

            const string name = "Test Name";
            sink.Name = name;
            Assert.Equal(0, string.Compare(sink.Name, name, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// Make sure we throw an exception if the transport delegate is null
        /// </summary>
        [Fact]
        public void TestConstructorNullSendDataDelegate()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                var transportSink = new BuildEventArgTransportSink(null);
            });
        }
        /// <summary>
        /// Verify consume throws the correct exception when a null build event is passed in
        /// </summary>
        [Fact]
        public void TestConsumeNullBuildEvent()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                BuildEventArgTransportSink transportSink = new BuildEventArgTransportSink(PacketProcessor);
                transportSink.Consume(null, 0);
            });
        }
        /// <summary>
        /// Verify consume properly packages up the message event into a packet and send it to the 
        /// transport delegate
        /// </summary>
        [Fact]
        public void TestConsumeMessageBuildEvent()
        {
            bool wentInHandler = false;
            BuildMessageEventArgs messageEvent = new BuildMessageEventArgs("My message", "Help me keyword", "Sender", MessageImportance.High);

            void TransportDelegate(INodePacket packet)
            {
                wentInHandler = true;
                LogMessagePacket loggingPacket = packet as LogMessagePacket;
                Assert.NotNull(loggingPacket);
                BuildMessageEventArgs messageEventFromPacket = loggingPacket.NodeBuildEvent.Value.Value as BuildMessageEventArgs;
                Assert.Equal(messageEventFromPacket, messageEvent);
            }

            BuildEventArgTransportSink transportSink = new BuildEventArgTransportSink(TransportDelegate);
            transportSink.Consume(messageEvent, 0);
            Assert.True(wentInHandler); // "Expected to go into transport delegate"
        }

        /// <summary>
        /// Verify consume ignores BuildStarted events
        /// </summary>
        [Fact]
        public void TestConsumeBuildStartedEvent()
        {
            bool wentInHandler = false;
            BuildStartedEventArgs buildStarted = new BuildStartedEventArgs("Start", "Help");

            void TransportDelegate(INodePacket packet)
            {
                wentInHandler = true;
            }

            BuildEventArgTransportSink transportSink = new BuildEventArgTransportSink(TransportDelegate);
            transportSink.Consume(buildStarted, 0);
            Assert.True(transportSink.HaveLoggedBuildStartedEvent);
            Assert.False(transportSink.HaveLoggedBuildFinishedEvent);
            Assert.False(wentInHandler); // "Expected not to go into transport delegate"
        }

        /// <summary>
        /// Verify consume ignores BuildFinished events
        /// </summary>
        [Fact]
        public void TestConsumeBuildFinishedEvent()
        {
            bool wentInHandler = false;
            BuildFinishedEventArgs buildFinished = new BuildFinishedEventArgs("Finished", "Help", true);

            void TransportDelegate(INodePacket packet)
            {
                wentInHandler = true;
            }

            BuildEventArgTransportSink transportSink = new BuildEventArgTransportSink(TransportDelegate);
            transportSink.Consume(buildFinished, 0);
            Assert.False(transportSink.HaveLoggedBuildStartedEvent);
            Assert.True(transportSink.HaveLoggedBuildFinishedEvent);
            Assert.False(wentInHandler); // "Expected not to go into transport delegate"
        }

        /// <summary>
        /// Make sure shutdown will correctly null out the send data delegate
        /// </summary>
        [WindowsFullFrameworkOnlyFact("https://github.com/dotnet/msbuild/issues/282")]
        public void TestShutDown()
        {
            SendDataDelegate transportDelegate = new(PacketProcessor);
            var weakTransportDelegateReference = new WeakReference(transportDelegate);
            var transportSink = new BuildEventArgTransportSink(transportDelegate);

            transportSink.ShutDown();

            Assert.NotNull(weakTransportDelegateReference.Target);
            transportDelegate = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();

            // Expected shutdown to null out the sendData delegate, the two garbage collections
            // should have collected the sendDataDelegate causing the weak reference to die.
            Assert.Null(weakTransportDelegateReference.Target);  // " Expected delegate to be dead"
        }

        /// <summary>
        /// Create a method which will be a fake method to process a packet.
        /// This needs to be done because using an anonymous method does not work. 
        /// Using an anonymous method does not work because when the delegate is created
        /// it seems that a field is created which creates a strong reference
        /// between the delegate and the class it was created in. This means the delegate is not
        /// garbage collected until the class it was instantiated in is collected itself.
        /// </summary>
        /// <param name="packet">Packet to process</param>
        private static void PacketProcessor(INodePacket packet)
        {
        }
    }
}
