using Serilog.Events;
using LogViewer.Core;
using Microsoft.Extensions.Logging;
using Serilog.Core;
using System.Globalization;

namespace Serilog.Sinks.LogView.Core;

public class DataStoreLoggerSink(
    Func<ILogDataStore> dataStoreProvider,
    Func<DataStoreLoggerConfiguration>? getCurrentConfig = null,
    IFormatProvider? formatProvider = null) : ILogEventSink
{
    protected Func<ILogDataStore> DataStoreProvider { get; } = dataStoreProvider;
    private IFormatProvider? FormatProvider { get; } = formatProvider;
    private Func<DataStoreLoggerConfiguration>? GetCurrentConfig { get; } = getCurrentConfig;

    public void Emit(LogEvent logEvent)
    {
        LogLevel logLevel = logEvent.Level switch
        {
            LogEventLevel.Verbose => LogLevel.Trace,
            LogEventLevel.Debug => LogLevel.Debug,
            LogEventLevel.Warning => LogLevel.Warning,
            LogEventLevel.Error => LogLevel.Error,
            LogEventLevel.Fatal => LogLevel.Critical,
            _ => LogLevel.Information
        };

        DataStoreLoggerConfiguration config = GetCurrentConfig?.Invoke() ?? new DataStoreLoggerConfiguration();

        EventId eventId = EventIdFactory(logEvent);
        if (eventId.Id == 0 && config.EventId != 0)
            eventId = config.EventId;

        string message = logEvent.RenderMessage(FormatProvider);
        
        string exception = logEvent.Exception?.Message ?? (logEvent.Level >= LogEventLevel.Error ? message : string.Empty);

        LogEntryColor color = config.Colors[logLevel];

        AddLogEntry(logLevel, eventId, message, exception, color);
    }

    protected virtual void AddLogEntry(LogLevel logLevel, EventId eventId, string message, string exception, LogEntryColor color)
    {
        ILogDataStore? dataStore = DataStoreProvider.Invoke();

        // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
        if (dataStore == null)
            return; // app is shutting down

        dataStore.AddEntry(new()
        {
            Timestamp = DateTime.UtcNow,
            LogLevel = logLevel,
            EventId = eventId,
            State = message,
            Exception = exception,
            Color = color
        });
    }

    private static EventId EventIdFactory(LogEvent logEvent)
    {
        EventId eventId;
        if (!logEvent.Properties.TryGetValue("EventId", out LogEventPropertyValue? src))
            return new();
        
        int? id = null;
        string? eventName = null;

        // ref: https://stackoverflow.com/a/56722516
        StructureValue? value = src as StructureValue;

        LogEventProperty? idProperty = value!.Properties.FirstOrDefault(x => x.Name.Equals("Id", StringComparison.Ordinal));
        if (idProperty is not null)
            id = int.Parse(idProperty.Value.ToString(),CultureInfo.InvariantCulture);

        LogEventProperty? nameProperty = value.Properties.FirstOrDefault(x => x.Name.Equals("Name", StringComparison.Ordinal));
        if (nameProperty is not null)
            eventName = nameProperty.Value.ToString().Trim('"');

        eventId = new EventId(id ?? 0, eventName ?? string.Empty);

        return eventId;
    }
}