using Microsoft.Extensions.Logging;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;

/// <summary>
///     Driver.AbLegacy-005 — verifies the driver accepts and uses an optional
///     <see cref="ILogger{AbLegacyDriver}"/>. Probe transitions, init failures, and the first
///     non-zero libplctag status per device must be logged (rather than only folded into the
///     transient <see cref="DriverHealth.Detail"/> string) so a field operator can correlate a
///     PCCC comms problem with a structured log entry.
/// </summary>
[Trait("Category", "Unit")]
public sealed class AbLegacyLoggerInjectionTests
{
    private sealed class CapturingLogger : ILogger<AbLegacyDriver>
    {
        public readonly List<(LogLevel Level, string Message)> Entries = new();

        /// <inheritdoc />
        public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;

        /// <inheritdoc />
        public bool IsEnabled(LogLevel logLevel) => true;

        /// <inheritdoc />
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
            Func<TState, Exception?, string> formatter)
            => Entries.Add((logLevel, formatter(state, exception)));
        private sealed class NullScope : IDisposable
        {
            public static readonly NullScope Instance = new();

            /// <inheritdoc />
            public void Dispose() { }
        }
    }

    /// <summary>Verifies that the driver accepts an optional logger parameter.</summary>
    [Fact]
    public void Driver_accepts_optional_logger_parameter()
    {
        // Constructor must accept an ILogger<AbLegacyDriver> as an optional named arg, matching
        // the Modbus/S7/Galaxy driver pattern. The driver runs with NullLogger when omitted.
        var logger = new CapturingLogger();
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions(), "drv-logged", tagFactory: null, logger: logger);
        drv.ShouldNotBeNull();
    }

    /// <summary>Verifies that driver initialization failure emits an error log.</summary>
    [Fact]
    public async Task InitializeAsync_failure_emits_error_log()
    {
        var logger = new CapturingLogger();
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions
        {
            Devices = [new AbLegacyDeviceOptions("not-a-valid-address")],
        }, "drv-logged", tagFactory: null, logger: logger);

        await Should.ThrowAsync<InvalidOperationException>(
            () => drv.InitializeAsync("{}", CancellationToken.None));

        var errors = logger.Entries.Where(e => e.Level >= LogLevel.Error).ToList();
        errors.ShouldNotBeEmpty("init failure must surface as an Error-level log");
        errors[0].Message.ShouldContain("drv-logged");
    }

    /// <summary>Verifies that the first non-zero libplctag status per device is logged.</summary>
    [Fact]
    public async Task First_nonzero_libplctag_status_per_device_is_logged()
    {
        // The driver should log the first occurrence of a non-zero libplctag status per device,
        // so that a field operator can correlate a comms problem with a structured log entry
        // even though Detail is overwritten by the next read.
        var factory = new FakeAbLegacyTagFactory();
        var logger = new CapturingLogger();
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions
        {
            Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
            RawTags = AbLegacyRawTags.Entries([new AbLegacyTagDefinition("X", "ab://10.0.0.5/1,0", "N7:0", AbLegacyDataType.Int)]),
            Probe = new AbLegacyProbeOptions { Enabled = false },
        }, "drv-logged", factory, logger);
        await drv.InitializeAsync("{}", CancellationToken.None);
        factory.Customise = p => new FakeAbLegacyTag(p) { Status = -32 }; // timeout

        await drv.ReadAsync(["X"], CancellationToken.None);

        var warnings = logger.Entries.Where(e => e.Level == LogLevel.Warning).ToList();
        warnings.ShouldNotBeEmpty("first non-zero libplctag status must emit a structured warning");
        warnings.Any(w => w.Message.Contains("ab://10.0.0.5/1,0") || w.Message.Contains("drv-logged"))
            .ShouldBeTrue("warning must identify the device or driver instance");

        // Second read with the same status — the per-device de-dupe should suppress.
        var warningCountAfterFirst = warnings.Count;
        await drv.ReadAsync(["X"], CancellationToken.None);
        var newWarnings = logger.Entries.Where(e => e.Level == LogLevel.Warning).ToList();
        newWarnings.Count.ShouldBe(warningCountAfterFirst, "subsequent same-device non-zero status stays quiet");
    }
}
