// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using FluentAssertions;
using Microsoft.DotNet.Cli.Sln.Internal;
using Microsoft.DotNet.TestFramework;
using Microsoft.DotNet.Tools.Test.Utilities;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Xunit;

namespace Microsoft.DotNet.Migration.Tests
{
    public class GivenThatIWantToMigrateSolutions : TestBase
    {
        [Theory]
        [InlineData("PJAppWithSlnVersion14", "Visual Studio 15", "15.0.26114.2", "10.0.40219.1")]
        [InlineData("PJAppWithSlnVersion15", "Visual Studio 15 Custom", "15.9.12345.4", "10.9.1234.5")]
        [InlineData("PJAppWithSlnVersionUnknown", "Visual Studio 15", "15.0.26114.2", "10.0.40219.1")]
        public void ItMigratesSlnAndEnsuresAtLeastVS15(
            string projectName,
            string productDescription,
            string visualStudioVersion,
            string minVisualStudioVersion)
        {
            var projectDirectory = TestAssets
                .GetProjectJson(TestAssetKinds.NonRestoredTestProjects, projectName)
                .CreateInstance(identifier: projectName)
                .WithSourceFiles()
                .WithEmptyGlobalJson()
                .Root;

            var solutionRelPath = "TestApp.sln";

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"migrate \"{solutionRelPath}\"")
                .Should().Pass();

            SlnFile slnFile = SlnFile.Read(Path.Combine(projectDirectory.FullName, solutionRelPath));
            slnFile.ProductDescription.Should().Be(productDescription);
            slnFile.VisualStudioVersion.Should().Be(visualStudioVersion);
            slnFile.MinimumVisualStudioVersion.Should().Be(minVisualStudioVersion);
        }

        [Fact]
        public void ItMigratesAndBuildsSln()
        {
            MigrateAndBuild(
                "NonRestoredTestProjects",
                "PJAppWithSlnAndXprojRefs");
        }

        [Fact]
        public void ItOnlyMigratesProjectsInTheSlnFile()
        {
            var projectDirectory = TestAssets
                .GetProjectJson(TestAssetKinds.NonRestoredTestProjects, "PJAppWithSlnAndXprojRefs")
                .CreateInstance()
                .WithSourceFiles()
                .WithEmptyGlobalJson()
                .Root;

            var solutionRelPath = Path.Combine("TestApp", "TestApp.sln");

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"migrate \"{solutionRelPath}\"")
                .Should().Pass();

            projectDirectory
                .Should().HaveFiles(new []
                    {
                        Path.Combine("TestApp", "TestApp.csproj"),
                        Path.Combine("TestLibrary", "TestLibrary.csproj"),
                        Path.Combine("TestApp", "src", "subdir", "subdir.csproj"),
                        Path.Combine("TestApp", "TestAssets", "TestAsset", "project.json")
                    });
 
            projectDirectory
                .Should().NotHaveFile(Path.Combine("TestApp", "TestAssets", "TestAsset", "TestAsset.csproj"));
        }

        [Fact]
        public void WhenDirectoryAlreadyContainsCsprojFileItMigratesAndBuildsSln()
        {
            MigrateAndBuild(
                "NonRestoredTestProjects",
                "PJAppWithSlnAndXprojRefsAndUnrelatedCsproj");
        }

        [Fact]
        public void WhenXprojReferencesCsprojAndSlnDoesNotItMigratesAndBuildsSln()
        {
            MigrateAndBuild(
                "NonRestoredTestProjects",
                "PJAppWithSlnThatDoesNotRefCsproj");
        }

        [Fact]
        public void WhenSolutionContainsACsprojFileItGetsMovedToBackup()
        {
            var projectDirectory = TestAssets
                .GetProjectJson("NonRestoredTestProjects", "PJAppWithSlnAndOneAlreadyMigratedCsproj")
                .CreateInstance()
                .WithSourceFiles()
                .WithEmptyGlobalJson()
                .Root;

            var solutionRelPath = Path.Combine("TestApp", "TestApp.sln");

            var cmd = new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .ExecuteWithCapturedOutput($"migrate \"{solutionRelPath}\"");

            cmd.Should().Pass();

            projectDirectory
                .GetDirectory("TestLibrary")
                .GetFile("TestLibrary.csproj")
                .Should().Exist();

            projectDirectory
                .GetDirectory("TestLibrary")
                .GetFile("TestLibrary.csproj.migration_in_place_backup")
                .Should().NotExist();

            projectDirectory
                .GetDirectory("backup", "TestLibrary")
                .GetFile("TestLibrary.csproj")
                .Should().Exist();
        }

        [Fact]
        public void WhenSolutionContainsACsprojFileItDoesNotTryToAddItAgain()
        {
            var projectDirectory = TestAssets
                .GetProjectJson(TestAssetKinds.NonRestoredTestProjects, "PJAppWithSlnAndOneAlreadyMigratedCsproj")
                .CreateInstance()
                .WithSourceFiles()
                .WithEmptyGlobalJson()
                .Root;

            var solutionRelPath = Path.Combine("TestApp", "TestApp.sln");

            var cmd = new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .ExecuteWithCapturedOutput($"migrate \"{solutionRelPath}\"");

            cmd.Should().Pass();
            cmd.StdOut.Should().NotContain("already contains project");
            cmd.StdErr.Should().BeEmpty();
        }

        [Theory]
        [InlineData("NoSolutionItemsAfterMigration.sln", false)]
        [InlineData("ReadmeSolutionItemAfterMigration.sln", true)]
        public void WhenMigratingAnSlnLinksReferencingItemsMovedToBackupAreRemoved(
            string slnFileName,
            bool solutionItemsContainsReadme)
        {
            var projectDirectory = TestAssets
                .GetProjectJson(TestAssetKinds.NonRestoredTestProjects, "PJAppWithSlnAndSolutionItemsToMoveToBackup")
                .CreateInstance(Path.GetFileNameWithoutExtension(slnFileName))
                .WithSourceFiles()
                .Root
                .FullName;

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"migrate \"{slnFileName}\"")
                .Should().Pass();

            var slnFile = SlnFile.Read(Path.Combine(projectDirectory, slnFileName));
            var solutionFolders = slnFile.Projects.Where(p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid);
            if (solutionItemsContainsReadme)
            {
                solutionFolders.Count().Should().Be(1);
                var solutionItems = solutionFolders.Single().Sections.GetSection("SolutionItems");
                solutionItems.Should().NotBeNull();
                solutionItems.Properties.Count().Should().Be(1);
                solutionItems.Properties["readme.txt"].Should().Be("readme.txt");
            }
            else
            {
                solutionFolders.Count().Should().Be(0);
            }
        }

        [Fact]
        public void ItMigratesSolutionInTheFolderWhenWeRunMigrationInThatFolder()
        {
            var projectDirectory = TestAssets
                .Get("NonRestoredTestProjects", "PJAppWithSlnAndXprojRefs")
                .CreateInstance()
                .WithSourceFiles()
                .WithEmptyGlobalJson()
                .Root;

            var workingDirectory = new DirectoryInfo(Path.Combine(projectDirectory.FullName, "TestApp"));
            var solutionRelPath = Path.Combine("TestApp", "TestApp.sln");

            new DotnetCommand()
                .WithWorkingDirectory(workingDirectory)
                .Execute($"migrate")
                .Should().Pass();

            SlnFile slnFile = SlnFile.Read(Path.Combine(projectDirectory.FullName, solutionRelPath));

            var nonSolutionFolderProjects = slnFile.Projects
                .Where(p => p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid);

            nonSolutionFolderProjects.Count().Should().Be(4);

            var slnProject = nonSolutionFolderProjects.Where((p) => p.Name == "TestApp").Single();
            slnProject.TypeGuid.Should().Be(ProjectTypeGuids.CSharpProjectTypeGuid);
            slnProject.FilePath.Should().Be("TestApp.csproj");

            slnProject = nonSolutionFolderProjects.Where((p) => p.Name == "TestLibrary").Single();
            slnProject.TypeGuid.Should().Be(ProjectTypeGuids.CSharpProjectTypeGuid);
            slnProject.FilePath.Should().Be(Path.Combine("..", "TestLibrary", "TestLibrary.csproj"));

            slnProject = nonSolutionFolderProjects.Where((p) => p.Name == "subdir").Single();
            slnProject.FilePath.Should().Be(Path.Combine("src", "subdir", "subdir.csproj"));

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"restore \"{solutionRelPath}\"")
                .Should().Pass();

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"build \"{solutionRelPath}\"")
                .Should().Pass();
        }

        [Fact]
        public void WhenXprojNameIsDifferentThanDirNameItGetsRemovedFromSln()
        {
            var projectDirectory = TestAssets
                .Get("NonRestoredTestProjects", "PJAppWithXprojNameDifferentThanDirName")
                .CreateInstance()
                .WithSourceFiles()
                .Root;

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"migrate")
                .Should().Pass();

            var slnFile = SlnFile.Read(Path.Combine(projectDirectory.FullName, "FolderHasDifferentName.sln"));
            slnFile.Projects.Count.Should().Be(1);
            slnFile.Projects[0].FilePath.Should().Be("PJAppWithXprojNameDifferentThanDirName.csproj");
        }

        private void MigrateAndBuild(string groupName, string projectName, [CallerMemberName] string callingMethod = "", string identifier = "")
        {
            var projectDirectory = TestAssets
                .Get(groupName, projectName)
                .CreateInstance(callingMethod: callingMethod, identifier: identifier)
                .WithSourceFiles()
                .WithEmptyGlobalJson()
                .Root;

            var solutionRelPath = Path.Combine("TestApp", "TestApp.sln");

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"migrate \"{solutionRelPath}\"")
                .Should().Pass();

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"restore \"{solutionRelPath}\"")
                .Should().Pass();

            new DotnetCommand()
                .WithWorkingDirectory(projectDirectory)
                .Execute($"build \"{solutionRelPath}\"")
                .Should().Pass();

            SlnFile slnFile = SlnFile.Read(Path.Combine(projectDirectory.FullName, solutionRelPath));
            
            var nonSolutionFolderProjects = slnFile.Projects
                .Where(p => p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid);

            nonSolutionFolderProjects.Count().Should().Be(3);

            var slnProject = nonSolutionFolderProjects.Where((p) => p.Name == "TestApp").Single();
            slnProject.TypeGuid.Should().Be(ProjectTypeGuids.CSharpProjectTypeGuid);
            slnProject.FilePath.Should().Be("TestApp.csproj");

            slnProject = nonSolutionFolderProjects.Where((p) => p.Name == "TestLibrary").Single();
            slnProject.TypeGuid.Should().Be(ProjectTypeGuids.CSharpProjectTypeGuid);
            slnProject.FilePath.Should().Be(Path.Combine("..", "TestLibrary", "TestLibrary.csproj"));

            slnProject = nonSolutionFolderProjects.Where((p) => p.Name == "subdir").Single();
            //ISSUE: https://github.com/dotnet/sdk/issues/522
            //Once we have that change migrate will always burn in the C# guid
            //slnProject.TypeGuid.Should().Be(ProjectTypeGuids.CSharpProjectTypeGuid);
            slnProject.FilePath.Should().Be(Path.Combine("src", "subdir", "subdir.csproj"));
        }
    }
}
