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.IngestCachedTelemetry"/>.
/// Verifies the DTO→entity→actor→ack round-trip through the gRPC handler. A
/// tiny <c>EchoCachedIngestActor</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 SiteStreamIngestCachedTelemetryTests : 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 CachedTelemetryPacket NewPacket(Guid? eventId = null, Guid? trackedId = null)
    {
        var now = Timestamp.FromDateTime(
            DateTime.SpecifyKind(new DateTime(2026, 5, 20, 10, 0, 0), DateTimeKind.Utc));
        return new CachedTelemetryPacket
        {
            AuditEvent = new AuditEventDto
            {
                EventId = (eventId ?? Guid.NewGuid()).ToString(),
                OccurredAtUtc = now,
                Channel = "ApiOutbound",
                Kind = "CachedSubmit",
                Status = "Submitted",
                SourceSiteId = "site-1",
                CorrelationId = (trackedId ?? Guid.NewGuid()).ToString(),
            },
            Operational = new SiteCallOperationalDto
            {
                TrackedOperationId = (trackedId ?? Guid.NewGuid()).ToString(),
                Channel = "ApiOutbound",
                Target = "ERP.GetOrder",
                SourceSite = "site-1",
                Status = "Submitted",
                RetryCount = 0,
                CreatedAtUtc = now,
                UpdatedAtUtc = now,
            },
        };
    }

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

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

        var packets = Enumerable.Range(0, 3)
            .Select(_ => NewPacket())
            .ToList();

        var batch = new CachedTelemetryBatch();
        batch.Packets.AddRange(packets);

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

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

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

        var batch = new CachedTelemetryBatch();
        batch.Packets.Add(NewPacket());

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

        Assert.Empty(ack.AcceptedEventIds);
    }

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