// 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.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Microsoft.AspNetCore.SignalR.Tests
{
    /// <summary>
    /// A logger factory that will prepend the current SignalR connection ID to the message.
    /// </summary>
    public class WrappingLoggerFactory : ILoggerFactory
    {
        private readonly ILoggerFactory _innerLoggerFactory;
        private readonly DummyProvider _provider;

        public WrappingLoggerFactory(ILoggerFactory innerLoggerFactory)
        {
            _innerLoggerFactory = innerLoggerFactory;
            _provider = new DummyProvider();
            AddProvider(_provider);
        }

        public void Dispose()
        {
            _innerLoggerFactory.Dispose();
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new WrappingLogger(_provider, _innerLoggerFactory.CreateLogger(categoryName));
        }

        public void AddProvider(ILoggerProvider provider)
        {
            _innerLoggerFactory.AddProvider(provider);
        }

        private class DummyProvider : ILoggerProvider, ISupportExternalScope
        {
            public IExternalScopeProvider ScopeProvider { get; private set; }

            public void Dispose()
            {
            }

            public ILogger CreateLogger(string categoryName)
            {
                return NullLogger.Instance;
            }

            public void SetScopeProvider(IExternalScopeProvider scopeProvider)
            {
                ScopeProvider = scopeProvider;
            }
        }

        private class WrappingLogger : ILogger
        {
            private readonly DummyProvider _provider;
            private readonly ILogger _logger;

            public WrappingLogger(DummyProvider provider, ILogger logger)
            {
                _provider = provider;
                _logger = logger;
            }

            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
                // Build the message outside of the formatter
                // Serilog doesn't appear to use the formatter and just writes the state
                var connectionId = GetConnectionId();

                var sb = new StringBuilder();
                if (connectionId != null)
                {
                    sb.Append(connectionId + " - ");
                }
                sb.Append(formatter(state, exception));
                var message = sb.ToString();

                _logger.Log(logLevel, eventId, message, exception, (s, ex) => s);
            }

            public bool IsEnabled(LogLevel logLevel)
            {
                return _logger.IsEnabled(logLevel);
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return _logger.BeginScope(state);
            }

            private string GetConnectionId()
            {
                string connectionId = null;
                _provider.ScopeProvider?.ForEachScope<object>((scope, s) =>
                {
                    if (scope is IReadOnlyList<KeyValuePair<string, object>> logScope)
                    {
                        if (logScope.FirstOrDefault(kv => kv.Key == "TransportConnectionId" || kv.Key == "ClientConnectionId").Value is string id)
                        {
                            connectionId = id;
                        }
                    }
                }, null);
                return connectionId;
            }
        }
    }
}