// 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 System.Collections.Generic;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;

#nullable disable

namespace Microsoft.Build.UnitTests
{
    public sealed class CallTarget_Tests : IDisposable
    {
        public CallTarget_Tests()
        {
            ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
        }

        public void Dispose()
        {
            ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
        }

        /// <summary>
        /// Simple test of the CallTarget task.
        /// </summary>
        [Fact]
        public void Simple()
        {
            MockLogger logger = ObjectModelHelpers.BuildProjectExpectSuccess(@"

                <Project DefaultTargets=`a` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
                    <Target Name=`a` >
	                    <CallTarget Targets=`b` />
                    </Target>
                    <Target Name=`b` >
	                    <Message Text=`Inside B` />
                    </Target>
                </Project>
                ");

            logger.AssertLogContains("Inside B");
        }

        /// <summary>
        /// Simple test of the CallTarget task, where one of the middle targets invoked fails.
        /// </summary>
        [Fact]
        public void FailedTargets()
        {
            MockLogger logger = ObjectModelHelpers.BuildProjectExpectFailure(@"

                <Project DefaultTargets=`build` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
                    <Target Name=`build` >
	                    <CallTarget Targets=`a; b; c` />
                    </Target>
                    <Target Name=`a` >
	                    <Message Text=`Inside A` />
                    </Target>
                    <Target Name=`b` >
	                    <Error Text=`Inside B` />
                    </Target>
                    <Target Name=`c` >
	                    <Message Text=`Inside C` />
                    </Target>
                </Project>
                ");

            logger.AssertLogContains("Inside A");
            logger.AssertLogContains("Inside B");

            // Target C should not have been run.
            logger.AssertLogDoesntContain("Inside C");
        }

        /// <summary>
        /// Test the CallTarget task, where one of the middle targets invoked fails, but we
        /// specified RunEachTargetSeparately, so all the targets should have been run anyway.
        /// </summary>
        [Fact]
        public void FailedTargetsRunSeparately()
        {
            MockLogger logger = ObjectModelHelpers.BuildProjectExpectFailure(@"

                <Project DefaultTargets=`build` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
                    <Target Name=`build` >
	                    <CallTarget Targets=`a; b; c` RunEachTargetSeparately=`true` />
                    </Target>
                    <Target Name=`a` >
	                    <Message Text=`Inside A` />
                    </Target>
                    <Target Name=`b` >
	                    <Error Text=`Inside B` />
                    </Target>
                    <Target Name=`c` >
	                    <Message Text=`Inside C` />
                    </Target>
                </Project>
                ");

            // All three targets should have been run.
            logger.AssertLogContains("Inside A");
            logger.AssertLogContains("Inside B");
            logger.AssertLogContains("Inside C");
        }

        [Fact]
        public void FailsWithOnlyTargetErrors()
        {
            MockLogger logger = ObjectModelHelpers.BuildProjectExpectFailure(@"
                <Project>
                  <Target Name='Init'>
                    <CallTarget Targets='Inside' />
                  </Target>
                  <Target Name='Inside'>
                    <Error />
                  </Target>
                </Project>");

            logger.ErrorCount.ShouldBe(1);
        }

        /// <summary>
        /// Test the CallTarget task, where we don't pass in any targets.  This is expected
        /// to succeed, so that callers of the task don't have to add a Condition to ensure
        /// that the list of targets is non-empty.
        /// </summary>
        [Fact]
        public void NoTargets()
        {
            ObjectModelHelpers.BuildProjectExpectSuccess(@"

                <Project DefaultTargets=`build` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
                    <Target Name=`build` >
	                    <CallTarget Targets=` @(empty) ` />
                    </Target>
                </Project>
                ");
        }

        /// <summary>
        /// Test the CallTarget task and capture the outputs of the invoked targets.
        /// </summary>
        [Fact]
        public void CaptureTargetOutputs()
        {
            Project project = ObjectModelHelpers.CreateInMemoryProject(@"

                <Project DefaultTargets=`build` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
                    <Target Name=`build` >

                        <CallTarget Targets=` a; b; c ` >
                            <Output ItemName=`myfancytargetoutputs` TaskParameter=`TargetOutputs`/>
                        </CallTarget>

                    </Target>
                    <!-- include some nice characters that need escaping -->
                    <Target Name=`a` Outputs=`a.t!@#$%^xt`>
	                    <Message Text=`Inside A` />
                    </Target>
                    <Target Name=`b` Outputs=`b.txt`>
	                    <Message Text=`Inside B` />
                    </Target>
                    <Target Name=`c` Outputs=`c.txt`>
	                    <Message Text=`Inside C` />
                    </Target>
                </Project>

                ");

            ProjectInstance instance = project.CreateProjectInstance();
            bool success = instance.Build();
            Assert.True(success); // "Build failed.  See test output (Attachments in Azure Pipelines) for details"

            IEnumerable<ProjectItemInstance> targetOutputs = instance.GetItems("myfancytargetoutputs");

            // Convert to a list of TaskItems for easier verification.
            List<ITaskItem> targetOutputsTaskItems = new List<ITaskItem>();
            foreach (ProjectItemInstance item in targetOutputs)
            {
                targetOutputsTaskItems.Add(new TaskItem(item.EvaluatedInclude));
            }

            // Order independent verification of the right set of items.
            ObjectModelHelpers.AssertItemsMatch(@"
                c.txt
                b.txt
                a.t!@#$%^xt
                ",
                targetOutputsTaskItems.ToArray(), false /* ignore the order */);
        }

        [Fact]
        public void CaptureTargetOutputsRunningEachTargetSeparately()
        {
            MockLogger logger = ObjectModelHelpers.BuildProjectExpectSuccess(@"

                <Project DefaultTargets = `CallT` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
	                <Target Name = `a` Outputs = `a.txt`/>
	                <Target Name = `b` Outputs = `b.txt`/>
	                <Target Name = `c` Outputs = `c.txt`/>
	                <Target Name = `CallT`>
		                <CallTarget
			                Targets = `a;b;c`
			                RunEachTargetSeparately = `true`>
			                <Output TaskParameter= `TargetOutputs` ItemName = `TaskOut`/>
		                </CallTarget>
		                <Message Text = `CallTarget Outputs: @(TaskOut)`/>
	                </Target>
                </Project>
                ");

            // All three targets should have been run.
            logger.AssertLogContains("CallTarget Outputs: a.txt;b.txt;c.txt");
        }
    }
}
