﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.GenerateType;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.GenerateType;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
    public abstract partial class AbstractUserDiagnosticTest
    {
        // TODO: IInlineRenameService requires WPF (https://github.com/dotnet/roslyn/issues/46153)
        private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf
            .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService))
            .AddParts(
                typeof(MockDiagnosticUpdateSourceRegistrationService),
                typeof(TestGenerateTypeOptionsService),
                typeof(TestProjectManagementService));

        internal async Task TestWithMockedGenerateTypeDialog(
            string initial,
            string languageName,
            string typeName,
            string expected = null,
            bool isMissing = false,
            Accessibility accessibility = Accessibility.NotApplicable,
            TypeKind typeKind = TypeKind.Class,
            string projectName = null,
            bool isNewFile = false,
            string existingFilename = null,
            ImmutableArray<string> newFileFolderContainers = default,
            string fullFilePath = null,
            string newFileName = null,
            string assertClassName = null,
            bool checkIfUsingsIncluded = false,
            bool checkIfUsingsNotIncluded = false,
            string expectedTextWithUsings = null,
            string defaultNamespace = "",
            bool areFoldersValidIdentifiers = true,
            GenerateTypeDialogOptions assertGenerateTypeDialogOptions = null,
            IList<TypeKindOptions> assertTypeKindPresent = null,
            IList<TypeKindOptions> assertTypeKindAbsent = null,
            bool isCancelled = false)
        {
            using var workspace = TestWorkspace.IsWorkspaceElement(initial)
                ? TestWorkspace.Create(initial, composition: s_composition)
                : languageName == LanguageNames.CSharp
                  ? TestWorkspace.CreateCSharp(initial, composition: s_composition)
                  : TestWorkspace.CreateVisualBasic(initial, composition: s_composition);

            var testOptions = new TestParameters();
            var (diagnostics, actions, _) = await GetDiagnosticAndFixesAsync(workspace, testOptions);

            var testState = new GenerateTypeTestState(workspace, projectToBeModified: projectName, typeName, existingFilename);

            // Initialize the viewModel values
            testState.TestGenerateTypeOptionsService.SetGenerateTypeOptions(
                accessibility: accessibility,
                typeKind: typeKind,
                typeName: testState.TypeName,
                project: testState.ProjectToBeModified,
                isNewFile: isNewFile,
                newFileName: newFileName,
                folders: newFileFolderContainers,
                fullFilePath: fullFilePath,
                existingDocument: testState.ExistingDocument,
                areFoldersValidIdentifiers: areFoldersValidIdentifiers,
                isCancelled: isCancelled);

            testState.TestProjectManagementService.SetDefaultNamespace(
                defaultNamespace: defaultNamespace);

            var generateTypeDiagFixes = diagnostics.SingleOrDefault(df => GenerateTypeTestState.FixIds.Contains(df.Id));

            if (isMissing)
            {
                Assert.Empty(actions);
                return;
            }

            var fixActions = MassageActions(actions);
            Assert.False(fixActions.IsDefault);

            // Since the dialog option is always fed as the last CodeAction
            var index = fixActions.Count() - 1;
            var action = fixActions.ElementAt(index);

            Assert.Equal(action.Title, FeaturesResources.Generate_new_type);
            var operations = await action.GetOperationsAsync(
                workspace.CurrentSolution, new ProgressTracker(), CancellationToken.None);
            Tuple<Solution, Solution> oldSolutionAndNewSolution = null;

            if (!isNewFile)
            {
                oldSolutionAndNewSolution = await TestOperationsAsync(
                    testState.Workspace, expected, operations,
                    conflictSpans: ImmutableArray<TextSpan>.Empty,
                    renameSpans: ImmutableArray<TextSpan>.Empty,
                    warningSpans: ImmutableArray<TextSpan>.Empty,
                    navigationSpans: ImmutableArray<TextSpan>.Empty,
                    expectedChangedDocumentId: testState.ExistingDocument.Id);
            }
            else
            {
                oldSolutionAndNewSolution = await TestAddDocument(
                    testState.Workspace,
                    expected,
                    operations,
                    projectName != null,
                    testState.ProjectToBeModified.Id,
                    newFileFolderContainers,
                    newFileName);
            }

            if (checkIfUsingsIncluded)
            {
                Assert.NotNull(expectedTextWithUsings);
                await TestOperationsAsync(testState.Workspace, expectedTextWithUsings, operations,
                    conflictSpans: ImmutableArray<TextSpan>.Empty,
                    renameSpans: ImmutableArray<TextSpan>.Empty,
                    warningSpans: ImmutableArray<TextSpan>.Empty,
                    navigationSpans: ImmutableArray<TextSpan>.Empty,
                    expectedChangedDocumentId: testState.InvocationDocument.Id);
            }

            if (checkIfUsingsNotIncluded)
            {
                var oldSolution = oldSolutionAndNewSolution.Item1;
                var newSolution = oldSolutionAndNewSolution.Item2;
                var changedDocumentIds = SolutionUtilities.GetChangedDocuments(oldSolution, newSolution);

                Assert.False(changedDocumentIds.Contains(testState.InvocationDocument.Id));
            }

            // Added into a different project than the triggering project
            if (projectName != null)
            {
                var appliedChanges = await ApplyOperationsAndGetSolutionAsync(testState.Workspace, operations);
                var newSolution = appliedChanges.Item2;
                var triggeredProject = newSolution.GetProject(testState.TriggeredProject.Id);

                // Make sure the Project reference is present
                Assert.True(triggeredProject.ProjectReferences.Any(pr => pr.ProjectId == testState.ProjectToBeModified.Id));
            }

            // Assert Option Calculation
            if (assertClassName != null)
            {
                Assert.True(assertClassName == testState.TestGenerateTypeOptionsService.ClassName);
            }

            if (assertGenerateTypeDialogOptions != null || assertTypeKindPresent != null || assertTypeKindAbsent != null)
            {
                var generateTypeDialogOptions = testState.TestGenerateTypeOptionsService.GenerateTypeDialogOptions;

                if (assertGenerateTypeDialogOptions != null)
                {
                    Assert.True(assertGenerateTypeDialogOptions.IsPublicOnlyAccessibility == generateTypeDialogOptions.IsPublicOnlyAccessibility);
                    Assert.True(assertGenerateTypeDialogOptions.TypeKindOptions == generateTypeDialogOptions.TypeKindOptions);
                    Assert.True(assertGenerateTypeDialogOptions.IsAttribute == generateTypeDialogOptions.IsAttribute);
                }

                if (assertTypeKindPresent != null)
                {
                    foreach (var typeKindPresentEach in assertTypeKindPresent)
                    {
                        Assert.True((typeKindPresentEach & generateTypeDialogOptions.TypeKindOptions) != 0);
                    }
                }

                if (assertTypeKindAbsent != null)
                {
                    foreach (var typeKindPresentEach in assertTypeKindAbsent)
                    {
                        Assert.True((typeKindPresentEach & generateTypeDialogOptions.TypeKindOptions) == 0);
                    }
                }
            }
        }
    }
}
