using Microsoft.Extensions.Configuration;
using Serilog.Events;

namespace ZB.MOM.WW.ScadaBridge.Host.Tests;

/// <summary>
/// Host-011: <c>ScadaBridge:Logging:MinimumLevel</c> must actually drive the Serilog
/// minimum level. Previously the value was bound into <see cref="LoggingOptions"/>
/// but never read, so editing it had no effect.
/// </summary>
public class LoggerConfigurationTests
{
    private static IConfiguration BuildConfig(string? minimumLevel)
    {
        var values = new Dictionary<string, string?>();
        if (minimumLevel != null)
            values["ScadaBridge:Logging:MinimumLevel"] = minimumLevel;
        return new ConfigurationBuilder().AddInMemoryCollection(values).Build();
    }

    [Fact]
    public void MinimumLevel_Warning_SuppressesInformationLogs()
    {
        var sink = new InMemorySink();
        var logger = LoggerConfigurationFactory
            .Build(BuildConfig("Warning"), "Central", "central", "node1")
            .WriteTo.Sink(sink)
            .CreateLogger();

        logger.Information("info message");
        logger.Warning("warning message");

        Assert.Single(sink.LogEvents);
        Assert.Equal(LogEventLevel.Warning, sink.LogEvents[0].Level);
    }

    [Fact]
    public void MinimumLevel_Debug_AllowsDebugLogs()
    {
        var sink = new InMemorySink();
        var logger = LoggerConfigurationFactory
            .Build(BuildConfig("Debug"), "Site", "site-a", "node1")
            .WriteTo.Sink(sink)
            .CreateLogger();

        logger.Debug("debug message");

        Assert.Single(sink.LogEvents);
        Assert.Equal(LogEventLevel.Debug, sink.LogEvents[0].Level);
    }

    [Fact]
    public void MinimumLevel_Absent_DefaultsToInformation()
    {
        var sink = new InMemorySink();
        var logger = LoggerConfigurationFactory
            .Build(BuildConfig(null), "Central", "central", "node1")
            .WriteTo.Sink(sink)
            .CreateLogger();

        logger.Debug("debug message");
        logger.Information("info message");

        Assert.Single(sink.LogEvents);
        Assert.Equal(LogEventLevel.Information, sink.LogEvents[0].Level);
    }

    /// <summary>
    /// Host-022: an unrecognised <c>ScadaBridge:Logging:MinimumLevel</c> (e.g. a typo
    /// like "Informaiton") must NOT abort startup but MUST emit a one-shot warning
    /// naming the offending value and the fallback so the silent coercion is
    /// visible. Null/blank is treated as "unset" and silently defaults.
    /// </summary>
    [Fact]
    public void ParseLevel_UnrecognisedValue_FallsBackAndWarns()
    {
        var writer = new StringWriter();

        var result = LoggerConfigurationFactory.ParseLevel("Informaiton", writer);

        Assert.Equal(LogEventLevel.Information, result);
        var warning = writer.ToString();
        Assert.Contains("warning", warning, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("Informaiton", warning);
        Assert.Contains("Information", warning);
    }

    [Fact]
    public void ParseLevel_NullOrBlank_FallsBackSilently()
    {
        var writer = new StringWriter();

        var nullResult = LoggerConfigurationFactory.ParseLevel(null, writer);
        var blankResult = LoggerConfigurationFactory.ParseLevel("   ", writer);

        Assert.Equal(LogEventLevel.Information, nullResult);
        Assert.Equal(LogEventLevel.Information, blankResult);
        Assert.Empty(writer.ToString());
    }

    [Fact]
    public void ParseLevel_RecognisedValue_NoWarning()
    {
        var writer = new StringWriter();

        var result = LoggerConfigurationFactory.ParseLevel("Warning", writer);

        Assert.Equal(LogEventLevel.Warning, result);
        Assert.Empty(writer.ToString());
    }

    /// <summary>
    /// Host-020: <c>ScadaBridge:Logging:MinimumLevel</c> is the documented source
    /// of truth for the Serilog floor, and the explicit <c>MinimumLevel.Is</c>
    /// call deliberately runs after <c>ReadFrom.Configuration(...)</c> so a
    /// <c>Serilog:MinimumLevel</c> entry is overridden. To make that precedence
    /// visible — instead of silently swallowed — <see cref="LoggerConfigurationFactory.Build(IConfiguration,string,string,string,TextWriter)"/>
    /// writes a one-shot warning when both keys are present. The warning must
    /// name both values and point the operator at the documented key. When the
    /// Serilog key is absent the warning is silent.
    /// </summary>
    [Fact]
    public void Build_BothMinimumLevelKeysSet_WarnsAboutOverride()
    {
        var writer = new StringWriter();
        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["ScadaBridge:Logging:MinimumLevel"] = "Warning",
                ["Serilog:MinimumLevel"] = "Debug",
            })
            .Build();

        LoggerConfigurationFactory.Build(configuration, "Central", "central", "node1", writer);

        var warning = writer.ToString();
        Assert.Contains("warning", warning, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("Serilog:MinimumLevel", warning);
        Assert.Contains("ScadaBridge:Logging:MinimumLevel", warning);
        Assert.Contains("Debug", warning);
        Assert.Contains("Warning", warning);
    }

    [Fact]
    public void Build_OnlyScadaBridgeMinimumLevelSet_NoOverrideWarning()
    {
        var writer = new StringWriter();
        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["ScadaBridge:Logging:MinimumLevel"] = "Warning",
            })
            .Build();

        LoggerConfigurationFactory.Build(configuration, "Central", "central", "node1", writer);

        // No Serilog override -> no override-warning. (The ScadaBridge value is
        // a recognised level, so ParseLevel is silent too.)
        Assert.Empty(writer.ToString());
    }
}
