// 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 Microsoft.Build.Evaluation;
using Microsoft.Build.Shared;
using Microsoft.Build.UnitTests;

using Shouldly;

using Xunit;
using Xunit.Abstractions;
using Xunit.NetCore.Extensions;

#nullable disable

namespace Microsoft.Build.Tasks.UnitTests
{
    public sealed class OutputPathTests : IDisposable
    {
        private readonly ITestOutputHelper _output;
        private readonly string _projectRelativePath = Path.Combine("src", "test", "test.csproj");

        public OutputPathTests(ITestOutputHelper output)
        {
            _output = output;
            ObjectModelHelpers.DeleteTempProjectDirectory();
        }

        public void Dispose()
        {
            ObjectModelHelpers.DeleteTempProjectDirectory();
        }

        /// <summary>
        /// Test when both BaseOutputPath and OutputPath are not specified.
        /// </summary>
        [Fact]
        public void BothBaseOutputPathAndOutputPathWereNotSpecified()
        {
            // Arrange
            var baseOutputPath = "bin";

            var projectFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath,
$@"<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.props`/>

    <PropertyGroup>
        <Platform>AnyCPU</Platform>
        <Configuration>Debug</Configuration>
    </PropertyGroup>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.targets`/>
    <Target Name=`Build`/>

</Project>");

            // Act
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFilePath, touchProject: false);

            project.Build(new MockLogger(_output)).ShouldBeFalse();

            // Assert
            project.GetPropertyValue("BaseOutputPath").ShouldBe(baseOutputPath + '\\');
            project.GetPropertyValue("BaseOutputPathWasSpecified").ShouldBe(string.Empty);
            project.GetPropertyValue("_OutputPathWasMissing").ShouldBe("true");
        }

        /// <summary>
        /// Test when BaseOutputPath is specified without the OutputPath.
        /// </summary>
        [Fact]
        public void BaseOutputPathWasSpecifiedAndIsOverridable()
        {
            // Arrange
            var baseOutputPath = Path.Combine("build", "bin");

            var projectFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath,
$@"<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.props`/>

    <PropertyGroup>
        <Platform>AnyCPU</Platform>
        <Configuration>Debug</Configuration>
        <BaseOutputPath>{baseOutputPath}</BaseOutputPath>
    </PropertyGroup>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.targets`/>
    <Target Name=`Build`/>

</Project>");

            // Act
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFilePath, touchProject: false);

            project.Build(new MockLogger(_output)).ShouldBeTrue();

            // Assert
            project.GetPropertyValue("BaseOutputPath").ShouldBe(baseOutputPath.WithTrailingSlash());
            project.GetPropertyValue("BaseOutputPathWasSpecified").ShouldBe("true");
            project.GetPropertyValue("_OutputPathWasMissing").ShouldBe("true");
        }

        /// <summary>
        /// Test when both BaseOutputPath and OutputPath are specified.
        /// </summary>
        [Fact]
        public void BothBaseOutputPathAndOutputPathWereSpecified()
        {
            // Arrange
            var baseOutputPath = Path.Combine("build", "bin");
            var outputPath = Path.Combine("bin", "Debug");
            var outputPathAlt = Path.Combine("bin", "Release");

            var projectFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath,
$@"<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.props`/>

    <PropertyGroup>
        <Platform>AnyCPU</Platform>
        <Configuration>Debug</Configuration>
    </PropertyGroup>

    <PropertyGroup>
        <BaseOutputPath>{baseOutputPath}</BaseOutputPath>
        <OutputPath Condition=`'$(Platform)|$(Configuration)' == 'AnyCPU|Debug'`>{outputPath}</OutputPath>
        <OutputPath Condition=`'$(Platform)|$(Configuration)' == 'AnyCPU|Release'`>{outputPathAlt}</OutputPath>
    </PropertyGroup>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.targets`/>
    <Target Name=`Build`/>

</Project>");

            // Act
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFilePath, touchProject: false);

            project.Build(new MockLogger(_output)).ShouldBeTrue();

            // Assert
            project.GetPropertyValue("BaseOutputPath").ShouldBe(baseOutputPath.WithTrailingSlash());
            project.GetPropertyValue("OutputPath").ShouldBe(outputPath.WithTrailingSlash());
            project.GetPropertyValue("BaseOutputPathWasSpecified").ShouldBe("true");
            project.GetPropertyValue("_OutputPathWasMissing").ShouldBe(string.Empty);
        }

        /// <summary>
        /// Test for [MSBuild]::NormalizePath and [MSBuild]::NormalizeDirectory returning current directory instead of current Project directory.
        /// </summary>
        [WindowsOnlyFact(Skip = "Skipping this test for now until we have a consensus about this issue.")]
        public void MSBuildNormalizePathShouldReturnProjectDirectory()
        {
            // Arrange
            var configuration = "Debug";
            var baseOutputPath = "bin";

            var projectFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath,
$@"<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.props`/>

    <PropertyGroup Condition=`'$(OutputPath)' == ''`>
        <OutputPath>$([MSBuild]::NormalizeDirectory('{baseOutputPath}', '{configuration}'))</OutputPath>
    </PropertyGroup>

    <Import Project=`$(MSBuildToolsPath)\Microsoft.Common.targets`/>
    <Target Name=`Build`/>

</Project>");

            // Act
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFilePath, touchProject: false);

            project.Build(new MockLogger(_output)).ShouldBeTrue();

            // Assert
            project.GetPropertyValue("Configuration").ShouldBe(configuration);
            project.GetPropertyValue("BaseOutputPath").ShouldBe(baseOutputPath.WithTrailingSlash());

            var expectedOutputPath = FileUtilities.CombinePaths(project.DirectoryPath, baseOutputPath, configuration).WithTrailingSlash();
            project.GetPropertyValue("OutputPath").ShouldBe(expectedOutputPath);
        }
    }
}
