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

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage.MessageInterception;

[Export(typeof(InterceptorManager))]
internal sealed class DefaultInterceptorManager : InterceptorManager
{
    [Obsolete]
    private readonly IReadOnlyList<Lazy<MessageInterceptor, IInterceptMethodMetadata>> _lazyInterceptors;
    private readonly IReadOnlyList<Lazy<GenericMessageInterceptor, IInterceptMethodMetadata>> _lazyGenericInterceptors;

    [ImportingConstructor]
    public DefaultInterceptorManager(
#pragma warning disable CS0618 // Type or member is obsolete
        [ImportMany] IEnumerable<Lazy<MessageInterceptor, IInterceptMethodMetadata>> lazyInterceptors,
#pragma warning restore CS0618 // Type or member is obsolete
        [ImportMany] IEnumerable<Lazy<GenericMessageInterceptor, IInterceptMethodMetadata>> lazyGenericInterceptors)
    {
        _ = lazyInterceptors ?? throw new ArgumentNullException(nameof(lazyInterceptors));
#pragma warning disable CS0612 // Type or member is obsolete
        _lazyInterceptors = lazyInterceptors.ToList().AsReadOnly();
#pragma warning restore CS0612 // Type or member is obsolete

        _ = lazyGenericInterceptors ?? throw new ArgumentNullException(nameof(lazyGenericInterceptors));
        _lazyGenericInterceptors = lazyGenericInterceptors.ToList().AsReadOnly();
    }

    public override bool HasInterceptor(string methodName, string contentType)
    {
        if (string.IsNullOrEmpty(methodName))
        {
            throw new ArgumentException("Cannot be empty", nameof(methodName));
        }

        foreach (var interceptor in _lazyGenericInterceptors)
        {
            if (interceptor.Metadata.ContentTypes.Any(ct => contentType.Equals(ct, StringComparison.Ordinal)))
            {
                foreach (var method in interceptor.Metadata.InterceptMethods)
                {
                    if (method.Equals(methodName, StringComparison.Ordinal))
                    {
                        return true;
                    }
                }
            }
        }

#pragma warning disable CS0612 // Type or member is obsolete
        foreach (var interceptor in _lazyInterceptors)
#pragma warning restore CS0612 // Type or member is obsolete
        {
            if (interceptor.Metadata.ContentTypes.Any(ct => contentType.Equals(ct, StringComparison.Ordinal)))
            {
                foreach (var method in interceptor.Metadata.InterceptMethods)
                {
                    if (method.Equals(methodName, StringComparison.Ordinal))
                    {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public async override Task<TJsonToken?> ProcessGenericInterceptorsAsync<TJsonToken>(string methodName, TJsonToken message, string contentType, CancellationToken cancellationToken)
        where TJsonToken : default
    {
        _ = message ?? throw new ArgumentNullException(nameof(message));
        if (string.IsNullOrEmpty(methodName))
        {
            throw new ArgumentException("Cannot be empty", nameof(methodName));
        }

        if (string.IsNullOrEmpty(contentType))
        {
            throw new ArgumentException("Cannot be empty", nameof(contentType));
        }

        for (var i = 0; i < _lazyGenericInterceptors.Count; i++)
        {
            var interceptor = _lazyGenericInterceptors[i];
            if (CanInterceptMessage(methodName, contentType, interceptor.Metadata))
            {
                var result = await interceptor.Value.ApplyChangesAsync(message, contentType, cancellationToken);
                cancellationToken.ThrowIfCancellationRequested();
                if (result.UpdatedToken is null ||
                    EqualityComparer<TJsonToken>.Default.Equals(result.UpdatedToken, default!))
                {
                    // The interceptor has blocked this message
                    return default;
                }

                message = result.UpdatedToken;

                if (result.ChangedDocumentUri)
                {
                    // If the DocumentUri changes, we need to restart the loop
                    i = -1;
                    continue;
                }
            }
        }

        return message;
    }

    [Obsolete("Please move to GenericInterceptionMiddleLayer and generic interceptors.")]
    public override async Task<JToken?> ProcessInterceptorsAsync(string methodName, JToken message, string contentType, CancellationToken cancellationToken)
    {
        _ = message ?? throw new ArgumentNullException(nameof(message));
        if (string.IsNullOrEmpty(methodName))
        {
            throw new ArgumentException("Cannot be empty", nameof(methodName));
        }

        if (string.IsNullOrEmpty(contentType))
        {
            throw new ArgumentException("Cannot be empty", nameof(contentType));
        }

        for (var i = 0; i < _lazyInterceptors.Count; i++)
        {
            var interceptor = _lazyInterceptors[i];
            if (CanInterceptMessage(methodName, contentType, interceptor.Metadata))
            {
                var result = await interceptor.Value.ApplyChangesAsync(message, contentType, cancellationToken);
                cancellationToken.ThrowIfCancellationRequested();
                if (result.UpdatedToken is null)
                {
                    // The interceptor has blocked this message
                    return null;
                }

                message = result.UpdatedToken;

                if (result.ChangedDocumentUri)
                {
                    // If the DocumentUri changes, we need to restart the loop
                    i = -1;
                    continue;
                }
            }
        }

        return message;
    }

    private static bool CanInterceptMessage(string methodName, string contentType, IInterceptMethodMetadata metadata)
    {
        var handledMessages = metadata.InterceptMethods;
        var contentTypes = metadata.ContentTypes;

        return handledMessages.Any(m => methodName.Equals(m, StringComparison.Ordinal))
            && contentTypes.Any(ct => contentType.Equals(ct, StringComparison.Ordinal));
    }
}
