using EonaCat.LogStack.Core;
using System;

namespace EonaCat.LogStack.Logging;

// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

/// <summary>
/// Adapter that bridges EonaCat logging with Microsoft.Extensions.Logging
/// This allows EonaCatLogStack to be used where Microsoft.Extensions.Logging.ILogger is expected
/// </summary>
public sealed class MicrosoftExtensionsLoggerAdapter : Microsoft.Extensions.Logging.ILogger
{
    private readonly ILogger _eonaCatLogger;

    public MicrosoftExtensionsLoggerAdapter(ILogger eonaCatLogger)
    {
        _eonaCatLogger = eonaCatLogger ?? throw new ArgumentNullException(nameof(eonaCatLogger));
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        // EonaCat doesn't support scopes out of the box, but we can return a no-op disposable
        return new NoOpDisposable();
    }

    public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
    {
        var eonaCatLevel = ConvertLogLevel(logLevel);
        return _eonaCatLogger.IsEnabled(eonaCatLevel);
    }

    public void Log<TState>(
        Microsoft.Extensions.Logging.LogLevel logLevel,
        Microsoft.Extensions.Logging.EventId eventId,
        TState state,
        Exception exception,
        Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        var eonaCatLevel = ConvertLogLevel(logLevel);
        var message = formatter(state, exception);

        if (exception != null)
        {
            _eonaCatLogger.Log(eonaCatLevel, exception, message);
        }
        else
        {
            _eonaCatLogger.Log(eonaCatLevel, message);
        }
    }

    private static LogLevel ConvertLogLevel(Microsoft.Extensions.Logging.LogLevel msLevel)
    {
        return msLevel switch
        {
            Microsoft.Extensions.Logging.LogLevel.Trace => LogLevel.Trace,
            Microsoft.Extensions.Logging.LogLevel.Debug => LogLevel.Debug,
            Microsoft.Extensions.Logging.LogLevel.Information => LogLevel.Information,
            Microsoft.Extensions.Logging.LogLevel.Warning => LogLevel.Warning,
            Microsoft.Extensions.Logging.LogLevel.Error => LogLevel.Error,
            Microsoft.Extensions.Logging.LogLevel.Critical => LogLevel.Critical,
            _ => LogLevel.Information
        };
    }

    private sealed class NoOpDisposable : IDisposable
    {
        public void Dispose() { }
    }
}

/// <summary>
/// Factory adapter that implements Microsoft.Extensions.Logging.ILoggerFactory
/// </summary>
public sealed class MicrosoftExtensionsLoggerFactoryAdapter : Microsoft.Extensions.Logging.ILoggerFactory
{
    private readonly ILoggerFactory _eonaCatFactory;
    private volatile bool _isDisposed;

    public MicrosoftExtensionsLoggerFactoryAdapter(ILoggerFactory eonaCatFactory)
    {
        _eonaCatFactory = eonaCatFactory ?? throw new ArgumentNullException(nameof(eonaCatFactory));
    }

    public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
    {
        if (_isDisposed)
        {
            throw new ObjectDisposedException(nameof(MicrosoftExtensionsLoggerFactoryAdapter));
        }

        var eonaCatLogger = _eonaCatFactory.CreateLogger(categoryName);
        return new MicrosoftExtensionsLoggerAdapter(eonaCatLogger);
    }

    public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider)
    {
        // EonaCat uses Flows instead of providers, so this is a no-op
        // Providers should be configured through the ILoggerFactory.GetLogStack()
    }

    public void Dispose()
    {
        if (_isDisposed)
        {
            return;
        }

        _isDisposed = true;
        _eonaCatFactory.DisposeAsync().GetAwaiter().GetResult();
        GC.SuppressFinalize(this);
    }
}
