// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using FakeItEasy;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.TemplatePackage;
using Microsoft.TemplateEngine.Cli.Commands;
using Microsoft.TemplateEngine.Cli.TemplateSearch;
using Microsoft.TemplateEngine.Cli.UnitTests.CliMocks;
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
using Microsoft.TemplateEngine.Mocks;
using Microsoft.TemplateEngine.TestHelper;
using Microsoft.TemplateSearch.Common;
using Microsoft.TemplateSearch.Common.Abstractions;
using Microsoft.TemplateSearch.Common.Providers;
using Newtonsoft.Json.Linq;

namespace Microsoft.TemplateEngine.Cli.UnitTests
{
    public class TemplateSearchCoordinatorTests : BaseTest
    {
#pragma warning disable SA1308 // Variable names should not be prefixed
#pragma warning disable SA1311 // Static readonly fields should begin with upper-case letter
        private static readonly ITemplatePackageInfo s_packOneInfo = new MockTemplatePackageInfo("PackOne", "1.0.0");

        private static readonly ITemplatePackageInfo s_packTwoInfo = new MockTemplatePackageInfo("PackTwo", "1.6.0");

        private static readonly ITemplatePackageInfo s_packThreeInfo = new MockTemplatePackageInfo("PackThree", "2.1");

        private static readonly ITemplateInfo s_fooOneTemplate =
            new MockTemplateInfo("foo1", name: "MockFooTemplateOne", identity: "Mock.Foo.1", groupIdentity: "Mock.Foo", author: "TestAuthor")
                .WithClassifications("CSharp", "Library")
                .WithDescription("Mock Foo template one")
                .WithChoiceParameter("Framework", "netcoreapp3.0", "netcoreapp3.1")
                .WithTag("language", "C#")
                .WithTag("type", "project");

        private static readonly ITemplateInfo s_fooTwoTemplate =
            new MockTemplateInfo("foo2", name: "MockFooTemplateTwo", identity: "Mock.Foo.2", groupIdentity: "Mock.Foo")
                .WithClassifications("CSharp", "Console")
                .WithDescription("Mock Foo template two")
                .WithChoiceParameter("Framework", "netcoreapp2.0", "netcoreapp2.1", "netcoreapp3.1")
                .WithTag("language", "C#");

        private static readonly ITemplateInfo s_barCSharpTemplate =
            new MockTemplateInfo("barC", name: "MockBarCsharpTemplate", identity: "Mock.Bar.1.Csharp", groupIdentity: "Mock.Bar")
                .WithClassifications("CSharp")
                .WithDescription("Mock Bar CSharp template")
                .WithTag("language", "C#");

        private static readonly ITemplateInfo s_barFSharpTemplate =
            new MockTemplateInfo("barF", name: "MockBarFSharpTemplate", identity: "Mock.Bar.1.FSharp", groupIdentity: "Mock.Bar")
                .WithClassifications("FSharp")
                .WithDescription("Mock Bar FSharp template")
                .WithTag("language", "F#");
#pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter
#pragma warning restore SA1308 // Variable names should not be prefixed

        [Fact]
        public async Task CacheSearchNameMatchTest()
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search foo");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Equal(2, nugetSearchResults.SearchHits.Count);
                (ITemplatePackageInfo _, IReadOnlyList<ITemplateInfo> packOneMatchedTemplates) = Assert.Single(nugetSearchResults.SearchHits, pack => pack.PackageInfo.Name.Equals(s_packOneInfo.Name));
                (ITemplatePackageInfo _, IReadOnlyList<ITemplateInfo> packTwoMatchedTemplates) = Assert.Single(nugetSearchResults.SearchHits, pack => pack.PackageInfo.Name.Equals(s_packTwoInfo.Name));
                Assert.Single(packOneMatchedTemplates, t => string.Equals(t.Name, s_fooOneTemplate.Name));
                Assert.Single(packTwoMatchedTemplates, t => string.Equals(t.Name, s_fooTwoTemplate.Name));
            }
        }

        // check that the symbol name-value correctly matches.
        // The _fooOneTemplate is a non-match because of a framework choice param value mismatch.
        // But the _fooTwoTemplate matches because the framework choice is valid for that template.
#pragma warning disable xUnit1004 // Test methods should not be skipped
        [Fact(Skip = "Fails due to matching on template options is not implemented.")]
#pragma warning restore xUnit1004 // Test methods should not be skipped
        public async Task CacheSearchCliSymbolNameFilterTest()
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, true);
            string v2FileLocation = SetupTemplateCache(cacheLocation, true);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search foo --framework netcoreapp2.0");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Single(nugetSearchResults.SearchHits);

                (ITemplatePackageInfo _, IReadOnlyList<ITemplateInfo> packTwoMatchedTemplates) = Assert.Single(nugetSearchResults.SearchHits, pack => pack.PackageInfo.Name.Equals(s_packTwoInfo.Name));
                Assert.Single(packTwoMatchedTemplates, t => string.Equals(t.Name, s_fooTwoTemplate.Name));
            }
        }

        // test that an invalid symbol makes the search be a non-match
#pragma warning disable xUnit1004 // Test methods should not be skipped
        [Fact(Skip = "Fails due to matching on template options is not implemented.")]
#pragma warning restore xUnit1004 // Test methods should not be skipped
        public async Task CacheSearchCliSymbolNameMismatchFilterTest()
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, true);
            string v2FileLocation = SetupTemplateCache(cacheLocation, true);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search foo --tfm netcoreapp2.0");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Empty(nugetSearchResults.SearchHits);
            }
        }

        // Tests that the input language causes the correct match filtering.
        [Fact]
        public async Task CacheSearchLanguageFilterTest()
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search bar --language F#");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Single(nugetSearchResults.SearchHits);
                Assert.Single(nugetSearchResults.SearchHits[0].MatchedTemplates);
                Assert.Equal(s_packThreeInfo.Name, nugetSearchResults.SearchHits[0].PackageInfo.Name);
                Assert.Equal(s_barFSharpTemplate.Name, nugetSearchResults.SearchHits[0].MatchedTemplates[0].Name);
            }
        }

        [Theory]
        [InlineData("", "test", 1)]
        [InlineData("foo", "test", 1)]
        [InlineData("", "Wrong", 0)]
        public async Task CacheSearchAuthorFilterTest(string commandTemplate, string commandAuthor, int matchCount)
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search {commandTemplate} --author {commandAuthor}");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Equal(matchCount, nugetSearchResults.SearchHits.Count);
            }
        }

        [Theory]
        [InlineData("", "project", 1)]
        [InlineData("foo", "project", 1)]
        [InlineData("", "Wrong", 0)]
        public async Task CacheSearchTypeFilterTest(string commandTemplate, string commandType, int matchCount)
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);

            ParseResult parseResult = myCommand.Parse($"new search {commandTemplate} --type {commandType}");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Equal(matchCount, nugetSearchResults.SearchHits.Count);
            }
        }

        [Theory]
        [InlineData("", "Three", 1, 2)]
        [InlineData("barC", "Three", 1, 2)]
        [InlineData("foo", "Three", 0, 0)]
        [InlineData("", "Wrong", 0, 0)]
        public async Task CacheSearchPackageFilterTest(string commandTemplate, string commandPackage, int packMatchCount, int templateMatchCount)
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);

            ParseResult parseResult = myCommand.Parse($"new search {commandTemplate} --package {commandPackage}");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Equal(packMatchCount, nugetSearchResults.SearchHits.Count);
                if (packMatchCount != 0)
                {
                    Assert.Equal(templateMatchCount, nugetSearchResults.SearchHits.Single(res => res.PackageInfo.Name == s_packThreeInfo.Name).MatchedTemplates.Count);
                }
            }
        }

        [Theory]
        [InlineData("", "CSharp", 3, 3)]
        [InlineData("bar", "FSharp", 1, 1)]
        [InlineData("foo", "Library", 1, 1)]
        [InlineData("", "Wrong", 0, 0)]
        [InlineData("", "Lib", 0, 0)]
        public async Task CacheSearchTagFilterTest(string commandTemplate, string commandTag, int packMatchCount, int templateMatchCount)
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search {commandTemplate} --tag {commandTag}");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Equal(packMatchCount, nugetSearchResults.SearchHits.Count);
                if (packMatchCount != 0)
                {
                    Assert.Equal(templateMatchCount, nugetSearchResults.SearchHits.Sum(res => res.MatchedTemplates.Count));
                }
            }
        }

        [Fact]
        public async Task CacheSearchLanguageMismatchFilterTest()
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v1FileLocation = SetupDiscoveryMetadata(cacheLocation, false);
            string v2FileLocation = SetupTemplateCache(cacheLocation, false);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search bar --language VB");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            foreach (string? location in new[] { v1FileLocation, v2FileLocation })
            {
                A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(location);
                IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                    factory.GetPackFilter(args),
                    CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                    default);

                Assert.Single(searchResults);
                Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
                SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
                Assert.Empty(nugetSearchResults.SearchHits);
            }
        }

#pragma warning disable xUnit1004 // Test methods should not be skipped
        [Fact(Skip = "Not relevant due to matching on template options is not implemented.")]
#pragma warning restore xUnit1004 // Test methods should not be skipped
        public async Task CacheSkipInvalidTemplatesTest()
        {
            string cacheLocation = TestUtils.CreateTemporaryFolder();
            string v2FileLocation = SetupInvalidTemplateCache(cacheLocation);

            List<(Type, IIdentifiedComponent)> builtIns = BuiltInTemplatePackagesProviderFactory.GetComponents(RepoTemplatePackages);
            builtIns.Add((typeof(ITemplateSearchProviderFactory), new NuGetMetadataSearchProviderFactory()));

            ICliTemplateEngineHost host = CliTestHostFactory.GetVirtualHost(additionalComponents: builtIns);
            IEnvironment environment = A.Fake<IEnvironment>();
            var engineEnvironmentSettings = new EngineEnvironmentSettings(host, virtualizeSettings: true, environment: environment);
            var templatePackageManager = new TemplatePackageManager(engineEnvironmentSettings);

            NewCommand myCommand = (NewCommand)NewCommandFactory.Create("new", _ => host);
            ParseResult parseResult = myCommand.Parse($"new search --unknown");
            SearchCommandArgs args = new((SearchCommand)parseResult.CommandResult.Command, parseResult);

            IReadOnlyList<IManagedTemplatePackage> templatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, default);
            TemplateSearchCoordinator searchCoordinator = CliTemplateSearchCoordinatorFactory.CreateCliTemplateSearchCoordinator(engineEnvironmentSettings);
            CliSearchFiltersFactory factory = new(templatePackages);

            A.CallTo(() => environment.GetEnvironmentVariable("DOTNET_NEW_SEARCH_FILE_OVERRIDE")).Returns(v2FileLocation);
            IReadOnlyList<SearchResult> searchResults = await searchCoordinator.SearchAsync(
                factory.GetPackFilter(args),
                CliSearchFiltersFactory.GetMatchingTemplatesFilter(args),
                default);

            Assert.Single(searchResults);
            Assert.Single(searchResults, result => result.Provider.Factory.DisplayName == "NuGet.org");
            SearchResult nugetSearchResults = searchResults.Single(result => result.Provider.Factory.DisplayName == "NuGet.org");
            Assert.Empty(nugetSearchResults.SearchHits);
        }

        [Theory]
        [InlineData(12489, 3198, 1)]
        [InlineData(3198, 12489, -1)]
        [InlineData(124, 3198, -1)]
        [InlineData(3198, 124, 1)]
        [InlineData(0, 0, 0)]
        [InlineData(-10, 0, 0)]
        [InlineData(987, 0, 1)]
        [InlineData(0, 10, -1)]
        [InlineData(987, 1, 0)]
        [InlineData(123, 345, 0)]
        public void TestCompare(long x, long y, int expectedOutcome)
        {
            Assert.Equal(expectedOutcome, CliTemplateSearchCoordinator.SearchResultTableRow.TotalDownloadsComparer.Compare(x, y));
        }

#pragma warning disable CS0618 // Type or member is obsolete
        private static string SetupDiscoveryMetadata(string fileLocation, bool includehostData = false)

        {
            const string version = "1.0.0.0";

            List<ITemplateInfo> templateCache = new()
            {
                s_fooOneTemplate,
                s_fooTwoTemplate,
                s_barCSharpTemplate,
                s_barFSharpTemplate
            };

            Dictionary<string, PackToTemplateEntry> packToTemplateMap = new();

            List<TemplateIdentificationEntry> packOneTemplateInfo = new()
            {
                new TemplateIdentificationEntry(s_fooOneTemplate.Identity, s_fooOneTemplate.GroupIdentity)
            };
            packToTemplateMap[s_packOneInfo.Name] = new PackToTemplateEntry(s_packOneInfo.Version ?? "", packOneTemplateInfo);

            List<TemplateIdentificationEntry> packTwoTemplateInfo = new()
            {
                new TemplateIdentificationEntry(s_fooTwoTemplate.Identity, s_fooTwoTemplate.GroupIdentity)
            };
            packToTemplateMap[s_packTwoInfo.Name] = new PackToTemplateEntry(s_packTwoInfo.Version ?? "", packTwoTemplateInfo);

            List<TemplateIdentificationEntry> packThreeTemplateInfo = new()
            {
                new TemplateIdentificationEntry(s_barCSharpTemplate.Identity, s_barCSharpTemplate.GroupIdentity),
                new TemplateIdentificationEntry(s_barFSharpTemplate.Identity, s_barFSharpTemplate.GroupIdentity)
            };
            packToTemplateMap[s_packThreeInfo.Name] = new PackToTemplateEntry(s_packThreeInfo.Version ?? "", packThreeTemplateInfo);

            Dictionary<string, object> additionalData = new();

            if (includehostData)
            {
                Dictionary<string, string> frameworkParamSymbolInfo = new()
                {
                    { "longName", "framework" },
                    { "shortName", "f" }
                };

                HostSpecificTemplateData fooTemplateHostData = new MockHostSpecificTemplateData(
                    new Dictionary<string, IReadOnlyDictionary<string, string>>()
                    {
                        { "Framework", frameworkParamSymbolInfo }
                    }
                );

                Dictionary<string, HostSpecificTemplateData> cliHostData = new()
                {
                    { s_fooOneTemplate.Identity, fooTemplateHostData },
                    { s_fooTwoTemplate.Identity, fooTemplateHostData }
                };

                additionalData[CliHostSearchCacheData.DataName] = cliHostData;
            }

            TemplateDiscoveryMetadata discoveryMetadata = new(version, templateCache, packToTemplateMap, additionalData);
            string targetPath = Path.Combine(fileLocation, "searchCacheV1.json");
            File.WriteAllText(targetPath, discoveryMetadata.ToJObject().ToString());
            return Path.Combine(fileLocation, "searchCacheV1.json");
        }
#pragma warning restore CS0618 // Type or member is obsolete

        private static string SetupTemplateCache(string fileLocation, bool includehostData = false)
        {
            Dictionary<string, string> frameworkParamSymbolInfo = new()
                {
                    { "longName", "framework" },
                    { "shortName", "f" }
                };

            HostSpecificTemplateData fooTemplateHostData = new MockHostSpecificTemplateData(
                new Dictionary<string, IReadOnlyDictionary<string, string>>()
                {
                        { "Framework", frameworkParamSymbolInfo }
                }
            );

            Dictionary<string, object> additionalData = new()
            {
                { CliHostSearchCacheData.DataName, fooTemplateHostData }
            };
            List<ITemplateInfo> templateCache = new()
            {
                s_fooOneTemplate,
                s_fooTwoTemplate,
                s_barCSharpTemplate,
                s_barFSharpTemplate
            };

            var fooOneTemplateData = new TemplateSearchData(s_fooOneTemplate, includehostData ? additionalData : null);
            var fooTwoTemplateData = new TemplateSearchData(s_fooTwoTemplate, includehostData ? additionalData : null);
            var barCSharpTemplateData = new TemplateSearchData(s_barCSharpTemplate, null);
            var barFSharpTemplateData = new TemplateSearchData(s_barFSharpTemplate, null);

            var packOne = new TemplatePackageSearchData(s_packOneInfo, new[] { fooOneTemplateData });
            var packTwo = new TemplatePackageSearchData(s_packTwoInfo, new[] { fooTwoTemplateData });
            var packThree = new TemplatePackageSearchData(s_packThreeInfo, new[] { barCSharpTemplateData, barFSharpTemplateData });

            var cache = new TemplateSearchCache(new[] { packOne, packTwo, packThree });

            JObject toSerialize = JObject.FromObject(cache);
            string targetPath = Path.Combine(fileLocation, "searchCacheV2.json");
            File.WriteAllText(targetPath, toSerialize.ToString());
            return targetPath;
        }

        private static string SetupInvalidTemplateCache(string fileLocation)
        {
            var packOne = new TemplatePackageSearchData(new MockTemplatePackageInfo("PackOne", "1.0.0"), new[] { new TemplateSearchData(new MockTemplateInfo("foo", "foo", "foo").WithParameters("Config type", "Main type", "unknown")) });
            var cache = new TemplateSearchCache(new[] { packOne });

            JObject toSerialize = JObject.FromObject(cache);
            string targetPath = Path.Combine(fileLocation, "searchCacheV2.json");
            File.WriteAllText(targetPath, toSerialize.ToString());
            return targetPath;
        }
    }
}
