﻿// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
using Newtonsoft.Json.Linq;
using Roslyn.Test.Utilities;
using Xunit.Abstractions;

namespace Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests
{
    public abstract class AbstractLiveShareRequestHandlerTests : AbstractLanguageServerProtocolTests
    {
        private static readonly TestComposition s_composition = LiveShareTestCompositions.Features
            .AddParts(typeof(MockDocumentNavigationServiceFactory))
            .AddParts(typeof(TestWorkspaceRegistrationService))
            .AddParts(typeof(TestWorkspaceConfigurationService));

        protected AbstractLiveShareRequestHandlerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
        {
        }

        private class MockHostProtocolConverter : IHostProtocolConverter
        {
            private readonly Func<Uri, Uri> _uriConversionFunction;

            public MockHostProtocolConverter() => _uriConversionFunction = uri => uri;

            public MockHostProtocolConverter(Func<Uri, Uri> uriConversionFunction) => _uriConversionFunction = uriConversionFunction;

            public Uri FromProtocolUri(Uri uri) => _uriConversionFunction(uri);

            public bool IsContainedInRootFolders(Uri uriToCheck) => true;

            public bool IsKnownWorkspaceFile(Uri uriToCheck) => throw new NotImplementedException();

            public Task RegisterExternalFilesAsync(Uri[] filePaths) => Task.CompletedTask;

            public Uri ToProtocolUri(Uri uri) => uri;

            public bool TryGetExternalUris(string exernalUri, out Uri uri) => throw new NotImplementedException();
        }

        protected override TestComposition Composition => s_composition;

        protected static async Task<ResponseType> TestHandleAsync<RequestType, ResponseType>(Solution solution, RequestType request, string methodName)
        {
            var requestContext = new RequestContext<Solution>(solution, new MockHostProtocolConverter(), JObject.FromObject(new ClientCapabilities()));
            return await GetHandler<RequestType, ResponseType>(solution, methodName).HandleAsync(request, requestContext, CancellationToken.None);
        }

        protected static async Task<ResponseType> TestHandleAsync<RequestType, ResponseType>(Solution solution, RequestType request, string methodName, Func<Uri, Uri> uriMappingFunc)
        {
            var requestContext = new RequestContext<Solution>(solution, new MockHostProtocolConverter(uriMappingFunc), JObject.FromObject(new ClientCapabilities()));
            return await GetHandler<RequestType, ResponseType>(solution, methodName).HandleAsync(request, requestContext, CancellationToken.None);
        }

        protected static ILspRequestHandler<RequestType, ResponseType, Solution> GetHandler<RequestType, ResponseType>(Solution solution, string methodName)
        {
            var workspace = (TestWorkspace)solution.Workspace;
            var handlers = workspace.ExportProvider.GetExportedValues<ILspRequestHandler>(LiveShareConstants.RoslynContractName);
            return (ILspRequestHandler<RequestType, ResponseType, Solution>)handlers.Single(handler => handler is ILspRequestHandler<RequestType, ResponseType, Solution> && IsMatchingMethod(handler, methodName));

            // Since request handlers can have the same input and output types (especially with object), we need to also
            // check that the LSP method the handler is exported for matches the one we're requesting.
            static bool IsMatchingMethod(ILspRequestHandler handler, string methodName)
            {
                var attribute = (ExportLspRequestHandlerAttribute)Attribute.GetCustomAttribute(handler.GetType(), typeof(ExportLspRequestHandlerAttribute));
                return attribute?.MethodName == methodName;
            }
        }
    }
}
