using Akka.Actor;
using Akka.TestKit.Xunit2;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;

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

/// <summary>
/// Bundle D D2 tests for <see cref="SiteStreamGrpcServer.IngestAuditEvents"/>.
/// Verifies the DTO→entity→actor→ack round-trip through the gRPC handler.
/// A tiny <c>StubIngestActor</c> stands in for the central
/// <c>AuditLogIngestActor</c>, replying with the EventIds it received so the
/// test asserts the wiring without depending on MSSQL.
/// </summary>
public class SiteStreamIngestAuditEventsTests : TestKit
{
    private readonly ISiteStreamSubscriber _subscriber = Substitute.For<ISiteStreamSubscriber>();

    private SiteStreamGrpcServer CreateServer() =>
        new(_subscriber, NullLogger<SiteStreamGrpcServer>.Instance);

    private static ServerCallContext NewContext(CancellationToken ct = default)
    {
        var context = Substitute.For<ServerCallContext>();
        context.CancellationToken.Returns(ct);
        return context;
    }

    private static AuditEventDto NewDto(Guid? id = null) => new()
    {
        EventId = (id ?? Guid.NewGuid()).ToString(),
        OccurredAtUtc = Timestamp.FromDateTime(
            DateTime.SpecifyKind(new DateTime(2026, 5, 20, 10, 0, 0), DateTimeKind.Utc)),
        Channel = "ApiOutbound",
        Kind = "ApiCall",
        Status = "Delivered",
        SourceSiteId = "site-1",
    };

    [Fact]
    public async Task IngestAuditEvents_With_AuditIngestActor_Routes_To_Actor_Returns_Reply()
    {
        // Arrange — a stub actor that echoes every received EventId back.
        var stubActor = Sys.ActorOf(Props.Create(() => new EchoIngestActor()));

        var server = CreateServer();
        server.SetAuditIngestActor(stubActor);

        // Build a 3-event batch.
        var dtos = Enumerable.Range(0, 3).Select(_ => NewDto()).ToList();
        var batch = new AuditEventBatch();
        batch.Events.AddRange(dtos);

        // Act
        var ack = await server.IngestAuditEvents(batch, NewContext());

        // Assert — every dto's id appears in the ack, demonstrating end-to-
        // end routing through the actor.
        Assert.Equal(3, ack.AcceptedEventIds.Count);
        var expectedIds = dtos.Select(d => d.EventId).ToHashSet();
        Assert.True(expectedIds.SetEquals(ack.AcceptedEventIds.ToHashSet()));
    }

    [Fact]
    public async Task IngestAuditEvents_NoActor_Wired_ReturnsEmptyAck()
    {
        var server = CreateServer();
        // Intentionally do NOT call SetAuditIngestActor — simulates host
        // startup race window.

        var batch = new AuditEventBatch();
        batch.Events.Add(NewDto());

        var ack = await server.IngestAuditEvents(batch, NewContext());

        Assert.Empty(ack.AcceptedEventIds);
    }

    /// <summary>
    /// Tiny ReceiveActor that echoes every EventId in an incoming
    /// <see cref="IngestAuditEventsCommand"/> back as an
    /// <see cref="IngestAuditEventsReply"/>. Stands in for the central
    /// AuditLogIngestActor so this test never touches MSSQL.
    /// </summary>
    private sealed class EchoIngestActor : ReceiveActor
    {
        public EchoIngestActor()
        {
            Receive<IngestAuditEventsCommand>(cmd =>
            {
                var ids = cmd.Events.Select(e => e.EventId).ToList();
                Sender.Tell(new IngestAuditEventsReply(ids));
            });
        }
    }
}
