﻿// 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.

using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer.BrokeredServices.Services;
using Microsoft.Extensions.Logging;
using Microsoft.ServiceHub.Framework;
using Microsoft.VisualStudio.Shell.ServiceBroker;
using Microsoft.VisualStudio.Utilities.ServiceBroker;
using Nerdbank.Streams;

namespace Microsoft.CodeAnalysis.LanguageServer.BrokeredServices;

[Export, Shared]
internal class BrokeredServiceBridgeProvider
{
    private const string ServiceBrokerChannelName = "serviceBroker";

    private readonly ILogger _logger;
    private readonly TraceSource _brokeredServiceTraceSource;

    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public BrokeredServiceBridgeProvider(ILoggerFactory loggerFactory, BrokeredServiceTraceListener brokeredServiceTraceListener)
    {
        _logger = loggerFactory.CreateLogger<BrokeredServiceBridgeProvider>();
        _brokeredServiceTraceSource = brokeredServiceTraceListener.Source;
    }

    /// <summary>
    /// Creates the brokered service bridge to the remote process.
    /// We expose the services from our container to the remote and consume services
    /// from the remote by proffering them into our container.
    /// </summary>
    /// <param name="brokeredServicePipeName">the pipe name we use for the connection.</param>
    /// <param name="container">our local container.</param>
    /// <param name="cancellationToken">a cancellation token.</param>
    /// <returns>a task that represents the lifetime of the bridge.  It will complete when the bridge closes.</returns>
    public async Task SetupBrokeredServicesBridgeAsync(string brokeredServicePipeName, BrokeredServiceContainer container, CancellationToken cancellationToken)
    {
        _logger.LogDebug("Setting up brokered service bridge");
        using var bridgeStream = await ServerFactory.ConnectAsync(brokeredServicePipeName, cancellationToken);
        using var bridgeMxStream = await MultiplexingStream.CreateAsync(bridgeStream, cancellationToken);

        // Wait until the connection ends (so we don't dispose of the stream before it ends).
        await Task.WhenAll(ProfferServicesToRemoteAsync(), ConsumeServicesFromRemoteAsync());

        async Task ProfferServicesToRemoteAsync()
        {
            using var profferedServiceBrokerChannel = await bridgeMxStream.OfferChannelAsync(ServiceBrokerChannelName, cancellationToken);
            var serviceBroker = container.GetLimitedAccessServiceBroker(ServiceAudience.Local, ImmutableDictionary<string, string>.Empty, ClientCredentialsPolicy.RequestOverridesDefault);
            using IpcRelayServiceBroker relayServiceBroker = new(serviceBroker);

            FrameworkServices.RemoteServiceBroker
                .WithTraceSource(_brokeredServiceTraceSource)
                .ConstructRpc(relayServiceBroker, profferedServiceBrokerChannel);

            await relayServiceBroker.Completion;
        }

        async Task ConsumeServicesFromRemoteAsync()
        {
            using var consumingServiceBrokerChannel = await bridgeMxStream.AcceptChannelAsync(ServiceBrokerChannelName, cancellationToken);
            var remoteClient = FrameworkServices.RemoteServiceBroker
                .WithTraceSource(_brokeredServiceTraceSource)
                .ConstructRpc<IRemoteServiceBroker>(consumingServiceBrokerChannel);

            using (container.ProfferRemoteBroker(remoteClient, bridgeMxStream, ServiceSource.OtherProcessOnSameMachine, Descriptors.RemoteServicesToRegister.Keys.ToImmutableHashSet()))
            {
                await consumingServiceBrokerChannel.Completion;
            }
        }
    }
}
