// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.Helpers;

namespace Microsoft.Testing.Platform.Logging;

internal sealed class LoggerFactory(ILoggerProvider[] loggerProviders, LogLevel logLevel, IMonitor monitor) : ILoggerFactory, IDisposable
#if NETCOREAPP
#pragma warning disable SA1001 // Commas should be spaced correctly
    , IAsyncDisposable
#pragma warning restore SA1001 // Commas should be spaced correctly
#endif
{
    private readonly object _sync = new();
    private readonly ILoggerProvider[] _loggerProviders = loggerProviders;
    private readonly LogLevel _logLevel = logLevel;
    private readonly IMonitor _monitor = monitor;
    private readonly Dictionary<string, Logger> _loggers = [with(StringComparer.Ordinal)];

    public ILogger CreateLogger(string categoryName)
    {
        using (_monitor.Lock(_sync))
        {
            if (!_loggers.TryGetValue(categoryName, out Logger? logger))
            {
                logger = new Logger(CreateLoggers(categoryName), _logLevel);

                _loggers[categoryName] = logger;
            }

            return logger;
        }
    }

    private ILogger[] CreateLoggers(string categoryName)
    {
        List<ILogger> loggers = [with(_loggerProviders.Length)];
        foreach (ILoggerProvider loggerProvider in _loggerProviders)
        {
            loggers.Add(loggerProvider.CreateLogger(categoryName));
        }

        return [.. loggers];
    }

    public void Dispose()
    {
        // NOTE: Reachable through the explicit LoggerFactoryProxy disposal at the end of
        // CommonHost.RunAsync's finally block. Before that change, this method was never
        // executed at runtime, so anything that becomes externally observable here (provider
        // ordering, exceptions, side effects) must be considered against the live shutdown path.
        // FileLoggerProvider is intentionally skipped because CommonHost disposes it explicitly
        // on the preceding line.
        foreach (IDisposable disposable in _loggerProviders.OfType<IDisposable>())
        {
            // FileLoggerProvider is special and needs to be disposed manually.
            if (disposable is FileLoggerProvider)
            {
                continue;
            }

            disposable.Dispose();
        }
    }

#if NETCOREAPP
    public async ValueTask DisposeAsync()
    {
        foreach (ILoggerProvider loggerProvider in _loggerProviders)
        {
            // FileLoggerProvider is special and needs to be disposed manually.
            if (loggerProvider is FileLoggerProvider)
            {
                continue;
            }

            if (loggerProvider is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync().ConfigureAwait(false);
            }
            else if (loggerProvider is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }
    }
#endif
}
