using Serilog;

namespace adas_core.Logging;

/// <summary>
/// Provides a static context for capturing and accessing logging information during the execution of an application.
/// </summary>
/// <remarks>
/// This class serves as a centralized holder for execution-scoped data that can be referenced by logging components throughout the application lifecycle.
/// </remarks>
public static class LogExecutionContext
{
    private static readonly AsyncLocal<string> AsyncLocalTraceIdentifier = new();

    public static string? TraceIdentifier => AsyncLocalTraceIdentifier.Value;

    /// Tries to set the trace identifier.
    /// 
    /// Trace identifier.
    /// If existing trace ID should be replaced (set to true ONLY if you receive and handle traced entities in a constant context)!
    public static bool TrySetTraceIdentifier(string traceIdentifier, bool force = false)
    {
        return TrySetValue(nameof(TraceIdentifier), traceIdentifier, AsyncLocalTraceIdentifier, string.IsNullOrEmpty,
            force);
    }

    /// <summary>
    /// Attempts to assign a new value to the ambient <see cref="AsyncLocal{T}"/> holder, applying validation rules and honoring a force-overwrite flag while logging a conflict when an execution flow already has a different value set.
    /// </summary>
    /// <param name="contextPropertyName">The logical name of the context property, used in the conflict log message for diagnostics.</param>
    /// <param name="newValue">The candidate value to apply; rejected when null or considered invalid by <paramref name="valueInvalidator"/>.</param>
    /// <param name="ambientHolder">The <see cref="AsyncLocal{T}"/> storage whose value is mutated when the assignment succeeds.</param>
    /// <param name="valueInvalidator">Predicate that returns true for values that must not be accepted, applied to both the new and current value.</param>
    /// <param name="force">When true, bypasses the existing-value check and always overwrites the ambient value.</param>
    /// <returns><c>true</c> if the new value was applied to <paramref name="ambientHolder"/>; otherwise <c>false</c>, including when the candidate is invalid, the ambient already holds a different non-null valid value, or no change was needed.</returns>
    private static bool TrySetValue(
            string contextPropertyName,
            string newValue,
            AsyncLocal<string> ambientHolder,
            Func<string, bool> valueInvalidator,
            bool force)

    {
        if (newValue.Equals(null) || valueInvalidator.Invoke(newValue)) return false;

        var currentValue = ambientHolder.Value;
        if (force || currentValue is null || currentValue.Equals(null) || valueInvalidator.Invoke(currentValue))
        {
            ambientHolder.Value = newValue;
            return true;
        }

        if (!currentValue.Equals(newValue))
            Log.Error(
                "Tried to set different value for {ctx}, but it is already set for this execution flow -{contextPropertyName} please, check the execution context logic! Current value: {currentValue} ; rejected value: {newValue}"
                , contextPropertyName, contextPropertyName, currentValue, newValue);

        return false;
    }
}