// 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.IO;
using System.Xml;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Shouldly;
using Xunit;

using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;

#nullable disable

namespace Microsoft.Build.UnitTests.OM.Construction
{
    /// <summary>
    /// Test the ProjectTargetElement class
    /// </summary>
    public class ProjectTargetElement_Tests
    {
        /// <summary>
        /// Create target with invalid name
        /// </summary>
        [Fact]
        public void AddTargetInvalidName()
        {
            Assert.Throws<ArgumentException>(() =>
            {
                ProjectRootElement project = ProjectRootElement.Create();
                project.CreateTargetElement("@#$invalid@#$");
            });
        }
        /// <summary>
        /// Read targets in an empty project
        /// </summary>
        [Fact]
        public void ReadNoTarget()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            Assert.Empty(project.Targets);
        }

        /// <summary>
        /// Read an empty target
        /// </summary>
        [Fact]
        public void ReadEmptyTarget()
        {
            string content = @"
                    <Project>
                        <Target Name='t'/>
                    </Project>
                ";

            using ProjectRootElementFromString projectRootElementFromString = new(content);
            ProjectRootElement project = projectRootElementFromString.Project;
            ProjectTargetElement target = (ProjectTargetElement)Helpers.GetFirst(project.Children);

            Assert.Equal(0, Helpers.Count(target.Children));
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Read attributes on the target element
        /// </summary>
        [Fact]
        public void ReadParameters()
        {
            ProjectTargetElement target = GetTargetXml();

            Assert.Equal("i", target.Inputs);
            Assert.Equal("o", target.Outputs);
            Assert.Equal("d", target.DependsOnTargets);
            Assert.Equal("c", target.Condition);
        }

        /// <summary>
        /// Set attributes on the target element
        /// </summary>
        [Fact]
        public void SetParameters()
        {
            ProjectTargetElement target = GetTargetXml();

            target.Inputs = "ib";
            target.Outputs = "ob";
            target.DependsOnTargets = "db";
            target.Condition = "cb";

            Assert.Equal("ib", target.Inputs);
            Assert.Equal("ob", target.Outputs);
            Assert.Equal("db", target.DependsOnTargets);
            Assert.Equal("cb", target.Condition);
        }

        /// <summary>
        /// Set null inputs on the target element
        /// </summary>
        [Fact]
        public void SetInvalidNullInputs()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                ProjectTargetElement target = GetTargetXml();
                target.Inputs = null;
            });
        }
        /// <summary>
        /// Set null outputs on the target element
        /// </summary>
        [Fact]
        public void SetInvalidNullOutputs()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                ProjectTargetElement target = GetTargetXml();
                target.Outputs = null;
            });
        }
        /// <summary>
        /// Set null dependsOnTargets on the target element
        /// </summary>
        [Fact]
        public void SetInvalidNullDependsOnTargets()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                ProjectTargetElement target = GetTargetXml();
                target.DependsOnTargets = null;
            });
        }
        /// <summary>
        /// Set null dependsOnTargets on the target element
        /// </summary>
        [Fact]
        public void SetInvalidNullKeepDuplicateOutputs()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                ProjectTargetElement target = GetTargetXml();
                target.KeepDuplicateOutputs = null;
            });
        }
        /// <summary>
        /// Set null condition on the target element
        /// </summary>
        [Fact]
        public void SetNullCondition()
        {
            ProjectTargetElement target = GetTargetXml();
            target.Condition = null;

            Assert.Equal(String.Empty, target.Condition);
        }

        /// <summary>
        /// Read a target with a missing name
        /// </summary>
        [Fact]
        public void ReadInvalidMissingName()
        {
            Assert.Throws<InvalidProjectFileException>(() =>
            {
                string content = @"
                    <Project>
                        <Target/>
                    </Project>
                ";

                ProjectRootElement.Create(XmlReader.Create(new StringReader(content)));
            });
        }
        /// <summary>
        /// Read a target with an invalid attribute
        /// </summary>
        [Fact]
        public void ReadInvalidAttribute()
        {
            Assert.Throws<InvalidProjectFileException>(() =>
            {
                string content = @"
                    <Project>
                        <Target XX='YY'/>
                    </Project>
                ";

                ProjectRootElement.Create(XmlReader.Create(new StringReader(content)));
            });
        }
        /// <summary>
        /// Read an target with two task children
        /// </summary>
        [Fact]
        public void ReadTargetTwoTasks()
        {
            ProjectTargetElement target = GetTargetXml();

            var tasks = Helpers.MakeList(target.Children);

            Assert.Equal(2, tasks.Count);

            ProjectTaskElement task1 = (ProjectTaskElement)tasks[0];
            ProjectTaskElement task2 = (ProjectTaskElement)tasks[1];

            Assert.Equal("t1", task1.Name);
            Assert.Equal("t2", task2.Name);
        }

        /// <summary>
        /// Set name
        /// </summary>
        [Fact]
        public void SetName()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.Name = "t2";

            Assert.Equal("t2", target.Name);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Set inputs
        /// </summary>
        [Fact]
        public void SetInputs()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.Inputs = "in";

            Assert.Equal("in", target.Inputs);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Set outputs
        /// </summary>
        [Fact]
        public void SetOutputs()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.Outputs = "out";

            Assert.Equal("out", target.Outputs);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Set dependsontargets
        /// </summary>
        [Fact]
        public void SetDependsOnTargets()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.DependsOnTargets = "dot";

            Assert.Equal("dot", target.DependsOnTargets);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Set condition
        /// </summary>
        [Fact]
        public void SetCondition()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.Condition = "c";

            Assert.Equal("c", target.Condition);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Set KeepDuplicateOutputs attribute
        /// </summary>
        [Fact]
        public void SetKeepDuplicateOutputs()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.KeepDuplicateOutputs = "true";

            Assert.Equal("true", target.KeepDuplicateOutputs);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Set return value.  Verify that setting to the empty string and null are
        /// both allowed and have distinct behaviour.
        /// </summary>
        [Fact]
        public void SetReturns()
        {
            ProjectRootElement project = ProjectRootElement.Create();
            ProjectTargetElement target = project.AddTarget("t");
            Helpers.ClearDirtyFlag(project);

            target.Returns = "@(a)";

            Assert.Equal("@(a)", target.Returns);
            Assert.True(project.HasUnsavedChanges);

            Helpers.ClearDirtyFlag(project);

            target.Returns = String.Empty;

            Assert.Equal(String.Empty, target.Returns);
            Assert.True(project.HasUnsavedChanges);

            Helpers.ClearDirtyFlag(project);

            target.Returns = null;

            Assert.Null(target.Returns);
            Assert.True(project.HasUnsavedChanges);
        }

        /// <summary>
        /// Parse invalid property under target
        /// </summary>
        [Fact]
        public void ReadInvalidPropertyUnderTarget()
        {
            using (TestEnvironment env = TestEnvironment.Create())
            {
                ChangeWaves.ResetStateForTests();

                string projectFile = @"
                    <Project>
                        <Target Name='t'>
                            <test>m</test>
                        </Target>
                    </Project>";
                TransientTestFile file = env.CreateFile("proj.csproj", projectFile);
                using ProjectCollection collection = new ProjectCollection();
                var error = Assert.Throws<InvalidProjectFileException>(() =>
                {
                    collection.LoadProject(file.Path).Build().ShouldBeTrue();
                });

                error.ErrorCode.ShouldMatch("MSB4067");
                var expectedString = "<PropertyGroup>";
                error.Message.ShouldMatch(expectedString);
            }
        }

        /// <summary>
        /// Helper to get an empty ProjectTargetElement with various attributes and two tasks
        /// </summary>
        private static ProjectTargetElement GetTargetXml()
        {
            string content = @"
                    <Project>
                        <Target Name='t' Inputs='i' Outputs='o' DependsOnTargets='d' Condition='c'>
                            <t1/>
                            <t2/>
                        </Target>
                    </Project>
                ";

            using ProjectRootElementFromString projectRootElementFromString = new(content);
            ProjectRootElement project = projectRootElementFromString.Project;
            ProjectTargetElement target = (ProjectTargetElement)Helpers.GetFirst(project.Children);
            return target;
        }
    }
}
