using System.Linq;
using Akka.Actor;
using Akka.Event;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Host.Actors;

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

/// <summary>
/// WP-15: Tests for DeadLetterMonitorActor.
/// </summary>
public class DeadLetterMonitorTests : TestKit
{
    private readonly ILogger<DeadLetterMonitorActor> _logger =
        NullLoggerFactory.Instance.CreateLogger<DeadLetterMonitorActor>();

    [Fact]
    public void DeadLetterMonitor_StartsWithZeroCount()
    {
        var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(_logger)));

        monitor.Tell(GetDeadLetterCount.Instance);
        var response = ExpectMsg<DeadLetterCountResponse>();

        Assert.Equal(0, response.Count);
    }

    [Fact]
    public void DeadLetterMonitor_IncrementsOnDeadLetter()
    {
        var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(_logger)));

        // Ensure actor has started and subscribed by sending a message and waiting for response
        monitor.Tell(GetDeadLetterCount.Instance);
        ExpectMsg<DeadLetterCountResponse>();

        // Now publish dead letters — actor is guaranteed to be subscribed
        Sys.EventStream.Publish(new DeadLetter("test-message-1", Sys.DeadLetters, Sys.DeadLetters));
        Sys.EventStream.Publish(new DeadLetter("test-message-2", Sys.DeadLetters, Sys.DeadLetters));

        // Use AwaitAssert to handle async event delivery
        AwaitAssert(() =>
        {
            monitor.Tell(GetDeadLetterCount.Instance);
            var response = ExpectMsg<DeadLetterCountResponse>();
            Assert.Equal(2, response.Count);
        });
    }

    [Fact]
    public async Task DeadLetterStorm_LogsAtMostWindowLimit_ButCountsAll()
    {
        // Review 01 [Low]: a failover/undeploy storm produced one Warning per
        // dead letter — unbounded log flood. Health metric counting is untouched.
        var logger = Substitute.For<ILogger<DeadLetterMonitorActor>>();
        var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(logger, null)));

        // Ensure the actor has started before the storm.
        monitor.Tell(GetDeadLetterCount.Instance);
        ExpectMsg<DeadLetterCountResponse>();

        for (var i = 0; i < 50; i++)
            monitor.Tell(new DeadLetter($"m{i}", TestActor, TestActor));

        var count = await monitor.Ask<DeadLetterCountResponse>(GetDeadLetterCount.Instance);
        Assert.Equal(50, count.Count); // every dead letter still counted

        var warnings = logger.ReceivedCalls().Count(c =>
            c.GetMethodInfo().Name == "Log"
            && (Microsoft.Extensions.Logging.LogLevel)c.GetArguments()[0]!
                == Microsoft.Extensions.Logging.LogLevel.Warning);
        // The per-letter cap plus the one "limit reached" notice.
        Assert.True(warnings <= DeadLetterMonitorActor.MaxWarningsPerWindow + 1,
            $"expected <= {DeadLetterMonitorActor.MaxWarningsPerWindow + 1} warnings, got {warnings}");
    }

    [Fact]
    public void DeadLetterMonitor_CountAccumulates()
    {
        var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(_logger)));

        // Ensure actor is started and subscribed
        monitor.Tell(GetDeadLetterCount.Instance);
        ExpectMsg<DeadLetterCountResponse>();

        // Send 5 dead letters
        for (var i = 0; i < 5; i++)
        {
            Sys.EventStream.Publish(
                new DeadLetter($"message-{i}", Sys.DeadLetters, Sys.DeadLetters));
        }

        AwaitAssert(() =>
        {
            monitor.Tell(GetDeadLetterCount.Instance);
            var response = ExpectMsg<DeadLetterCountResponse>();
            Assert.Equal(5, response.Count);
        });
    }
}
