using Microsoft.Extensions.Logging;
using System;

namespace EonaCat.LogStack.Logging;

/// <summary>
/// Microsoft.Extensions.Logging bridge. Works with ASP.NET Core, worker services,
/// console applications, VS extensions and any .NET project that uses ILogger.
/// </summary>
public sealed class EonaCatLoggerProvider : ILoggerProvider, ISupportExternalScope
{
    private readonly EonaCatLogStack _logStack;
    private IExternalScopeProvider? _scopeProvider;

    public EonaCatLoggerProvider(EonaCatLogStack logStack) => _logStack = logStack;

    public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
        => new CategoryLogger(categoryName, _logStack, _scopeProvider);

    public void SetScopeProvider(IExternalScopeProvider scopeProvider)
        => _scopeProvider = scopeProvider;

    public void Dispose() { }
}

internal sealed class CategoryLogger : Microsoft.Extensions.Logging.ILogger
{
    private readonly string _category;
    private readonly EonaCatLogStack _EonaCatLogStack;
    private readonly IExternalScopeProvider? _scopes;

    public CategoryLogger(string category, EonaCatLogStack EonaCatLogStack, IExternalScopeProvider? scopes)
    {
        _category = category;
        _EonaCatLogStack = EonaCatLogStack;
        _scopes = scopes;
    }

    public IDisposable BeginScope<TState>(TState state) where TState : notnull
        => _scopes?.Push(state) ?? NullScope.Instance;

    public bool IsEnabled(LogLevel logLevel)
        => logLevel != LogLevel.None;

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

        var message = formatter(state, exception);

        _scopes?.ForEachScope<object?>((scope, _) =>
        {
            message = $"{message} | Scope={scope}";
        }, null);

        if (exception != null)
        {
            message = $"{message} | Exception={exception}";
        }

        _EonaCatLogStack.Log($"[{logLevel}] [{_category}] {message}");
    }
}

internal sealed class NullScope : IDisposable
{
    public static readonly NullScope Instance = new();
    public void Dispose() { }
}
