// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the MIT license.  See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Reflection;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Tools.Analyzers;
using Microsoft.CodeAnalysis.Tools.Formatters;
using Microsoft.CodeAnalysis.Tools.Tests.Formatters;

namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers
{
    using static AnalyzerAssemblyGenerator;

    public class FilterDiagnosticsTests : CSharpFormatterTests
    {
        [Fact]
        public async Task TestFilterWarning()
        {
            var (_, solution) = await GetSolutionAsync();
            var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution);
            var project = solution.Projects.First();
            var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath);
            var minimumSeverity = DiagnosticSeverity.Warning;
            var diagnostics = ImmutableHashSet<string>.Empty;
            var excludeDiagnostics = ImmutableHashSet<string>.Empty;
            var result = await AnalyzerFormatter.FilterAnalyzersAsync(
                solution,
                projectAnalyzersAndFixers,
                formattablePaths,
                minimumSeverity,
                diagnostics,
                excludeDiagnostics,
                CancellationToken.None);
            var (_, analyzers) = Assert.Single(result);
            Assert.Single(analyzers);
        }

        [Fact]
        public async Task TestFilterError()
        {
            var (_, solution) = await GetSolutionAsync();
            var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution);
            var project = solution.Projects.First();
            var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath);
            var minimumSeverity = DiagnosticSeverity.Error;
            var diagnostics = ImmutableHashSet<string>.Empty;
            var excludeDiagnostics = ImmutableHashSet<string>.Empty;
            var result = await AnalyzerFormatter.FilterAnalyzersAsync(
                solution,
                projectAnalyzersAndFixers,
                formattablePaths,
                minimumSeverity,
                diagnostics,
                excludeDiagnostics,
                CancellationToken.None);
            var (_, analyzers) = Assert.Single(result);
            Assert.Empty(analyzers);
        }

        [Fact]
        public async Task TestFilterDiagnostics_NotInDiagnosticsList()
        {
            var (_, solution) = await GetSolutionAsync();
            var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution);
            var project = solution.Projects.First();
            var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath);
            var minimumSeverity = DiagnosticSeverity.Warning;
            var diagnostics = ImmutableHashSet.Create("IDE0005");
            var excludeDiagnostics = ImmutableHashSet<string>.Empty;
            var result = await AnalyzerFormatter.FilterAnalyzersAsync(
                solution,
                projectAnalyzersAndFixers,
                formattablePaths,
                minimumSeverity,
                diagnostics,
                excludeDiagnostics,
                CancellationToken.None);
            var (_, analyzers) = Assert.Single(result);
            Assert.Empty(analyzers);
        }

        [Fact]
        public async Task TestFilterDiagnostics_InDiagnosticsList()
        {
            var (_, solution) = await GetSolutionAsync();
            var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution);
            var project = solution.Projects.First();
            var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath);
            var minimumSeverity = DiagnosticSeverity.Warning;
            var diagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId");
            var excludeDiagnostics = ImmutableHashSet<string>.Empty;
            var result = await AnalyzerFormatter.FilterAnalyzersAsync(
                solution,
                projectAnalyzersAndFixers,
                formattablePaths,
                minimumSeverity,
                diagnostics,
                excludeDiagnostics,
                CancellationToken.None);
            var (_, analyzers) = Assert.Single(result);
            Assert.Single(analyzers);
        }

        [Fact]
        public async Task TestFilterDiagnostics_ExcludedFromDiagnosticsList()
        {
            var (_, solution) = await GetSolutionAsync();
            var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution);
            var project = solution.Projects.First();
            var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath);
            var minimumSeverity = DiagnosticSeverity.Warning;
            var diagnostics = ImmutableHashSet<string>.Empty;
            var excludeDiagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId");
            var result = await AnalyzerFormatter.FilterAnalyzersAsync(
                solution,
                projectAnalyzersAndFixers,
                formattablePaths,
                minimumSeverity,
                diagnostics,
                excludeDiagnostics,
                CancellationToken.None);
            var (_, analyzers) = Assert.Single(result);
            Assert.Empty(analyzers);
        }

        [Fact]
        public async Task TestFilterDiagnostics_ExcludeTrumpsInclude()
        {
            var (_, solution) = await GetSolutionAsync();
            var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution);
            var project = solution.Projects.First();
            var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath);
            var minimumSeverity = DiagnosticSeverity.Warning;
            var diagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId");
            var excludeDiagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId");
            var result = await AnalyzerFormatter.FilterAnalyzersAsync(
                solution,
                projectAnalyzersAndFixers,
                formattablePaths,
                minimumSeverity,
                diagnostics,
                excludeDiagnostics,
                CancellationToken.None);
            var (_, analyzers) = Assert.Single(result);
            Assert.Empty(analyzers);
        }

        private static async Task<AnalyzersAndFixers> GetAnalyzersAndFixersAsync(string language)
        {
            var assemblies = new[]
            {
                await GenerateAssemblyAsync(
                    GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"),
                    GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId"))
            };

            var analyzers = assemblies
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => typeof(DiagnosticAnalyzer).IsAssignableFrom(type))
                .Where(type => type.GetCustomAttribute<DiagnosticAnalyzerAttribute>(inherit: false) is { } attribute && attribute.Languages.Contains(language))
                .Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type))
                .OfType<DiagnosticAnalyzer>()
                .ToImmutableArray();

            var codeFixes = AnalyzerFinderHelpers.LoadFixers(assemblies, language);
            return new AnalyzersAndFixers(analyzers, codeFixes);
        }

        private Task<(Workspace workspace, Solution solution)> GetSolutionAsync()
        {
            var text = SourceText.From("");
            TestState.Sources.Add(text);

            var editorConfig = $@"
root = true
[*.cs]
dotnet_diagnostic.DiagnosticAnalyzerId.severity = warning
";

            return GetSolutionAsync(
                TestState.Sources.ToArray(),
                TestState.AdditionalFiles.ToArray(),
                TestState.AdditionalReferences.ToArray(),
                editorConfig);
        }

        private async Task<ImmutableDictionary<ProjectId, AnalyzersAndFixers>> GetProjectAnalyzersAndFixersAsync(Solution solution)
        {
            var analyzersByLanguage = new Dictionary<string, AnalyzersAndFixers>();
            var builder = ImmutableDictionary.CreateBuilder<ProjectId, AnalyzersAndFixers>();
            foreach (var project in solution.Projects)
            {
                if (!analyzersByLanguage.TryGetValue(project.Language, out var analyzersAndFixers))
                {
                    analyzersAndFixers = await GetAnalyzersAndFixersAsync(project.Language);
                    analyzersByLanguage.Add(project.Language, analyzersAndFixers);
                }

                builder.Add(project.Id, analyzersAndFixers);
            }

            return builder.ToImmutable();
        }

        private protected override ICodeFormatter Formatter { get; }
    }
}
