// 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 Microsoft.Build.Construction;
using Microsoft.DotNet.Tools.Test.Utilities;
using System.IO;
using System.Linq;
using Xunit;
using FluentAssertions;
using Microsoft.DotNet.ProjectJsonMigration.Rules;
using System;

namespace Microsoft.DotNet.ProjectJsonMigration.Tests
{
    public class GivenThatIWantToMigratePackOptions : TestBase
    {
        [Fact]
        public void ItDoesNotMigrateSummary()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""summary"": ""Some not important summary""
                    }
                }");

            EmitsOnlyAlwaysEmittedPackOptionsProperties(mockProj);
        }

        [Fact]
        public void ItDoesNotMigrateOwner()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""owner"": ""Some not important owner""
                    }
                }");

            EmitsOnlyAlwaysEmittedPackOptionsProperties(mockProj);
        }

        [Fact]
        public void MigratingEmptyTagsDoesNotPopulatePackageTags()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""tags"": []
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageTags").Should().Be(0);
        }

        [Fact]
        public void MigratingTagsPopulatesPackageTagsSemicolonDelimited()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""tags"": [""hyperscale"", ""cats""]
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageTags").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageTags").Value.Should().Be("hyperscale;cats");
        }

        [Fact]
        public void MigratingReleaseNotesPopulatesPackageReleaseNotes()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""releaseNotes"": ""Some release notes value.""
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageReleaseNotes").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageReleaseNotes").Value.Should()
                .Be("Some release notes value.");
        }

        [Fact]
        public void MigratingIconUrlPopulatesPackageIconUrl()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""iconUrl"": ""http://www.mylibrary.gov/favicon.ico""
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageIconUrl").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageIconUrl").Value.Should()
                .Be("http://www.mylibrary.gov/favicon.ico");
        }

        [Fact]
        public void MigratingProjectUrlPopulatesPackageProjectUrl()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""projectUrl"": ""http://www.url.to.library.com""
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageProjectUrl").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageProjectUrl").Value.Should()
                .Be("http://www.url.to.library.com");
        }

        [Fact]
        public void MigratingLicenseUrlPopulatesPackageLicenseUrl()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""licenseUrl"": ""http://www.url.to.library.com/licence""
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageLicenseUrl").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageLicenseUrl").Value.Should()
                .Be("http://www.url.to.library.com/licence");
        }

        [Fact]
        public void MigratingRequireLicenseAcceptancePopulatesPackageRequireLicenseAcceptance()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""requireLicenseAcceptance"": ""true""
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageRequireLicenseAcceptance").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageRequireLicenseAcceptance").Value.Should().Be("true");
        }

        [Fact]
        public void MigratingRequireLicenseAcceptancePopulatesPackageRequireLicenseAcceptanceEvenIfItsValueIsFalse()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""requireLicenseAcceptance"": ""false""
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "PackageRequireLicenseAcceptance").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "PackageRequireLicenseAcceptance").Value.Should().Be("false");
        }

        [Fact]
        public void MigratingRepositoryTypePopulatesRepositoryType()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""repository"": {
                            ""type"": ""git""
                        }
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "RepositoryType").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "RepositoryType").Value.Should().Be("git");
        }

        [Fact]
        public void MigratingRepositoryUrlPopulatesRepositoryUrl()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""repository"": {
                            ""url"": ""http://github.com/dotnet/cli""
                        }
                    }
                }");

            mockProj.Properties.Count(p => p.Name == "RepositoryUrl").Should().Be(1);
            mockProj.Properties.First(p => p.Name == "RepositoryUrl").Value.Should().Be("http://github.com/dotnet/cli");
        }

        [Fact]
        public void MigratingFilesWithoutMappingsPopulatesContentWithSamePathAsIncludeAndPackTrue()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""files"": {
                            ""include"": [""path/to/some/file.cs"", ""path/to/some/other/file.cs""]
                        }
                    }
                }");

            var contentItems = mockProj.Items
                .Where(item => item.ItemType.Equals("None", StringComparison.Ordinal))
                .Where(item => item.GetMetadataWithName("Pack").Value == "true");

            contentItems.Count().Should().Be(1);
            contentItems.First().Update.Should().Be(@"path\to\some\file.cs;path\to\some\other\file.cs");
        }

        [Fact]
        public void MigratingFilesWithExcludePopulatesNoneWithPackFalseForTheExcludedFiles()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""files"": {
                            ""include"": [""path/to/some/file.cs"", ""path/to/some/other/file.cs""],
                            ""exclude"": [""path/to/file/to/exclude.cs""]
                        }
                    }
                }");

            foreach (var item in mockProj.Items.Where(i => i.ItemType.Equals("None", StringComparison.Ordinal)))
            {
                Console.WriteLine($"Update: {item.Update}, Include: {item.Include}, Remove: {item.Remove}");
                foreach(var meta in item.Metadata)
                {
                    Console.WriteLine($"\tMetadata: Name: {meta.Name}, Value: {meta.Value}");
                }

                foreach(var condition in item.ConditionChain())
                {
                    Console.WriteLine($"\tCondition: {condition}");
                }
            }

            var contentItemsToInclude = mockProj.Items
                .Where(item => item.ItemType.Equals("None", StringComparison.Ordinal))
                .Where(item => item.GetMetadataWithName("Pack").Value == "true");

            contentItemsToInclude.Count().Should().Be(1);
            contentItemsToInclude.First().Update.Should().Be(@"path\to\some\file.cs;path\to\some\other\file.cs");

            var contentItemsToExclude = mockProj.Items
                .Where(item => item.ItemType.Equals("None", StringComparison.Ordinal))
                .Where(item => item.GetMetadataWithName("Pack").Value == "false");

            contentItemsToExclude.Count().Should().Be(1);
            contentItemsToExclude.First().Update.Should().Be(@"path\to\file\to\exclude.cs");
        }

        [Fact]
        public void MigratingFilesWithMappingsPopulatesContentPackagePathMetadata()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""files"": {
                            ""include"": [""path/to/some/file.cs""],
                            ""mappings"": {
                                ""some/other/path/file.cs"": ""path/to/some/file.cs""
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items
                .Where(item => item.ItemType.Equals("None", StringComparison.Ordinal))
                .Where(item =>
                    item.GetMetadataWithName("Pack").Value == "true" &&
                    item.GetMetadataWithName("PackagePath") != null);

            contentItems.Count().Should().Be(1);
            contentItems.First().Update.Should().Be(@"path\to\some\file.cs");
            contentItems.First().GetMetadataWithName("PackagePath").Value.Should().Be(
                Path.Combine("some", "other", "path"));
        }

        [Fact]
        public void MigratingFilesWithMappingsToRootPopulatesContentPackagePathMetadataButLeavesItEmpty()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""files"": {
                            ""include"": [""path/to/some/file.cs""],
                            ""mappings"": {
                                "".file.cs"": ""path/to/some/file.cs""
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items
                .Where(item => item.ItemType.Equals("None", StringComparison.Ordinal))
                .Where(item =>
                    item.GetMetadataWithName("Pack").Value == "true" &&
                    item.GetMetadataWithName("PackagePath") != null);

            contentItems.Count().Should().Be(1);
            contentItems.First().Update.Should().Be(@"path\to\some\file.cs");
            contentItems.First().GetMetadataWithName("PackagePath").Value.Should().BeEmpty();
        }

        [Fact]
        public void MigratingSameFileWithMultipleMappingsStringJoinsTheMappingsInPackagePath()
        {
            var mockProj = RunPackOptionsRuleOnPj(@"
                {
                    ""packOptions"": {
                        ""files"": {
                            ""include"": [""path/to/some/file.cs""],
                            ""mappings"": {
                                ""other/path/file.cs"": ""path/to/some/file.cs"",
                                ""different/path/file1.cs"": ""path/to/some/file.cs""
                            }
                        }
                    }
                }");

            var expectedPackagePath = string.Join(
                ";",
                new [] {
                    Path.Combine("different", "path"),
                    Path.Combine("other", "path")
                });

            var contentItems = mockProj.Items
                .Where(item => item.ItemType.Equals("None", StringComparison.Ordinal))
                .Where(item =>
                    item.GetMetadataWithName("Pack").Value == "true" &&
                    item.GetMetadataWithName("PackagePath") != null);

            contentItems.Count().Should().Be(1);
            contentItems.First().Update.Should().Be(@"path\to\some\file.cs");
            contentItems.First().GetMetadataWithName("PackagePath").Value.Should().Be(expectedPackagePath);
        }

        private ProjectRootElement RunPackOptionsRuleOnPj(string packOptions, string testDirectory = null)
        {
            testDirectory = testDirectory ?? Temp.CreateDirectory().Path;
            return TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[]
            {
                new MigratePackOptionsRule()
            }, packOptions, testDirectory);
        }

        private void EmitsOnlyAlwaysEmittedPackOptionsProperties(ProjectRootElement project)
        {
            project.Properties.Count().Should().Be(1);
            project.Properties.All(p => p.Name == "PackageRequireLicenseAcceptance").Should().BeTrue();
        }
    }
}
