using Serilog;
using Serilog.Core;
using Serilog.Events;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Resilience;

namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Observability;

[Trait("Category", "Integration")]
public sealed class CapabilityInvokerEnrichmentTests
{
    /// <summary>Verifies that InvokerExecute logs inside call site with structured properties.</summary>
    [Fact]
    public async Task InvokerExecute_LogsInsideCallSite_CarryStructuredProperties()
    {
        var sink = new InMemorySink();
        var logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .WriteTo.Sink(sink)
            .CreateLogger();

        var invoker = new CapabilityInvoker(
            new DriverResiliencePipelineBuilder(),
            driverInstanceId: "drv-live",
            optionsAccessor: () => new DriverResilienceOptions { Tier = DriverTier.A },
            driverType: "Modbus");

        await invoker.ExecuteAsync(
            DriverCapability.Read,
            "plc-1",
            ct =>
            {
                logger.Information("inside call site");
                return ValueTask.FromResult(42);
            },
            CancellationToken.None);

        var evt = sink.Events.ShouldHaveSingleItem();
        evt.Properties["DriverInstanceId"].ToString().ShouldBe("\"drv-live\"");
        evt.Properties["DriverType"].ToString().ShouldBe("\"Modbus\"");
        evt.Properties["CapabilityName"].ToString().ShouldBe("\"Read\"");
        evt.Properties.ShouldContainKey("CorrelationId");
    }

    /// <summary>Verifies that InvokerExecute does not leak context outside the call site.</summary>
    [Fact]
    public async Task InvokerExecute_DoesNotLeak_ContextOutsideCallSite()
    {
        var sink = new InMemorySink();
        var logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .WriteTo.Sink(sink)
            .CreateLogger();

        var invoker = new CapabilityInvoker(
            new DriverResiliencePipelineBuilder(),
            driverInstanceId: "drv-a",
            optionsAccessor: () => new DriverResilienceOptions { Tier = DriverTier.A });

        await invoker.ExecuteAsync(DriverCapability.Read, "host", _ => ValueTask.FromResult(1), CancellationToken.None);
        logger.Information("outside");

        var outside = sink.Events.ShouldHaveSingleItem();
        outside.Properties.ContainsKey("DriverInstanceId").ShouldBeFalse();
    }

    private sealed class InMemorySink : ILogEventSink
    {
        /// <summary>Gets the list of captured log events.</summary>
        public List<LogEvent> Events { get; } = [];
        /// <summary>Emits a log event by adding it to the captured events list.</summary>
        /// <param name="logEvent">The log event to emit.</param>
        public void Emit(LogEvent logEvent) => Events.Add(logEvent);
    }
}
