using System.Diagnostics;
using Akka.Actor;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;

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

/// <summary>
/// WP1.6: a send to a site with no configured gRPC channel must fail the caller's Ask immediately
/// (<see cref="Status.Failure"/> wrapping <see cref="SiteChannelUnavailableException"/>) instead of
/// warning-and-dropping the message and leaving the Ask to expire at its full timeout.
/// </summary>
public class GrpcSiteTransportFailFastTests : TestKit
{
    private static readonly CommunicationOptions Opts = new()
    {
        DeploymentTimeout = TimeSpan.FromSeconds(120),
        LifecycleTimeout = TimeSpan.FromSeconds(30),
        ArtifactDeploymentTimeout = TimeSpan.FromSeconds(60),
        QueryTimeout = TimeSpan.FromSeconds(30),
        IntegrationTimeout = TimeSpan.FromSeconds(30),
        DebugViewTimeout = TimeSpan.FromSeconds(10)
    };

    private static GrpcSiteTransport BuildTransport() => new(
        new SitePairChannelProvider(
            new NoKeyProvider(), Options.Create(Opts), NullLogger<SitePairChannelProvider>.Instance),
        Opts,
        NullLogger<GrpcSiteTransport>.Instance);

    [Fact]
    public void Send_ToUnconfiguredSite_FaultsTheAskFastInsteadOfTimingOut()
    {
        // No ReconcileSites/UpdateSite call was ever made for this site — exactly the "unknown
        // site" shape SitePairChannelProvider throws SiteChannelUnavailableException for.
        var transport = BuildTransport();
        var probe = CreateTestProbe();
        var envelope = new SiteEnvelope(
            "unconfigured-site",
            new EnableInstanceCommand("cmd-1", "Site1.Pump1", DateTimeOffset.UtcNow));

        var stopwatch = Stopwatch.StartNew();
        transport.Send(envelope, probe.Ref);

        // The old warn-and-drop behavior would leave the caller silent until its own Ask timeout
        // (30s in production). Well under 1s here proves the fail-fast path, not a lucky race.
        var failure = probe.ExpectMsg<Status.Failure>(TimeSpan.FromSeconds(1));
        stopwatch.Stop();

        Assert.True(stopwatch.Elapsed < TimeSpan.FromSeconds(1),
            $"expected the Ask to fault well under 1s; took {stopwatch.Elapsed}");
        var cause = Assert.IsType<SiteChannelUnavailableException>(failure.Cause);
        Assert.Equal("unconfigured-site", cause.SiteId);
        Assert.Contains("unconfigured-site", cause.Message);
    }

    [Fact]
    public async Task Ask_AgainstUnconfiguredSite_CompletesFaultedFastViaTellStatusFailure()
    {
        // Mirrors how CommunicationService actually calls this seam: Ask a temp actor, transport
        // Tells it back. Proves the fail-fast reply satisfies a real Akka Ask, not just a TestProbe.
        var transport = BuildTransport();
        var askActor = Sys.ActorOf(Props.Create(() => new EchoingAskTarget(transport)));

        var stopwatch = Stopwatch.StartNew();
        var ex = await Assert.ThrowsAsync<SiteChannelUnavailableException>(async () =>
            await askActor.Ask<object>(
                new SiteEnvelope("unconfigured-site", new EnableInstanceCommand(
                    "cmd-1", "Site1.Pump1", DateTimeOffset.UtcNow)),
                TimeSpan.FromSeconds(30)));
        stopwatch.Stop();

        Assert.Equal("unconfigured-site", ex.SiteId);
        Assert.True(stopwatch.Elapsed < TimeSpan.FromSeconds(1),
            $"expected the Ask to fault well under the 30s timeout; took {stopwatch.Elapsed}");
    }

    /// <summary>Relays an Ask into the transport, exactly as <c>CentralCommunicationActor</c> does.</summary>
    private sealed class EchoingAskTarget : ReceiveActor
    {
        public EchoingAskTarget(GrpcSiteTransport transport)
        {
            Receive<SiteEnvelope>(env => transport.Send(env, Sender));
        }
    }

    private sealed class NoKeyProvider : ISitePskProvider
    {
        public ValueTask<string> GetAsync(string siteId, CancellationToken ct) => new("k");
        public void Invalidate(string siteId) { }
    }
}
