using Akka.Actor;
using Akka.Cluster;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
using ZB.MOM.WW.ScadaBridge.TestSupport;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Actors;

/// <summary>
/// UA1: cert-trust reconcile-on-join. When a site node (re)joins the cluster, the
/// Deployment Manager singleton reads its own trusted-peer store and pushes each
/// trusted certificate to the joining node's <see cref="CertStoreActor"/> so the
/// two per-node PKI stores converge — without this, a node that missed a trust
/// broadcast while it was down stays permanently divergent.
///
/// Needs a cluster provider (the DM subscribes to <c>MemberUp</c> in PreStart and
/// addresses the joined node via a remote actor path), so this lives in its own
/// cluster-enabled ActorSystem rather than the non-cluster DeploymentManagerActorTests.
/// </summary>
public class DeploymentManagerCertReconcileTests : TestKit, IDisposable
{
    // In-memory TestTransport (not dot-netty): the cluster extension loads and
    // SelfAddress is available, but no socket is bound and the node never has to
    // form a real two-node cluster. Role must be "Site" (SiteClusterRole).
    private const string ClusterConfig = @"
akka {
    actor { provider = cluster }
    remote {
        enabled-transports = [""akka.remote.test""]
        test {
            transport-class = ""Akka.Remote.Transport.TestTransport, Akka.Remote""
            applied-adapters = []
            registry-key = dm-cert-reconcile-test
            local-address = ""test://dm-cert@localhost:1""
            maximum-payload-bytes = 128000b
            scheme-identifier = test
        }
    }
    cluster { roles = [""Site""] }
    loglevel = WARNING
}";

    private readonly SiteStorageService _storage;
    private readonly ScriptCompilationService _compilationService;
    private readonly SharedScriptLibrary _sharedScriptLibrary;
    private readonly TestLocalDb _localDb;

    public DeploymentManagerCertReconcileTests() : base(ClusterConfig, "dm-cert-reconcile")
    {
        _localDb = TestLocalDb.CreateTemp("dm-cert-test");
        _storage = new SiteStorageService(
            _localDb.Db, NullLogger<SiteStorageService>.Instance);
        _storage.InitializeAsync().GetAwaiter().GetResult();
        _compilationService = new ScriptCompilationService(
            NullLogger<ScriptCompilationService>.Instance);
        _sharedScriptLibrary = new SharedScriptLibrary(
            _compilationService, NullLogger<SharedScriptLibrary>.Instance);
    }

    void IDisposable.Dispose()
    {
        // TestKit teardown first, so no in-flight actor can reach a disposed ILocalDb;
        // then dispose the database before deleting — the master connection anchors the WAL.
        Shutdown();
        var path = _localDb.Path;
        _localDb.Dispose();
        TestLocalDb.DeleteFiles(path);
    }

    /// <summary>Forwards every message it receives to a probe, preserving the original sender.</summary>
    private sealed class ForwardingActor : ReceiveActor
    {
        public ForwardingActor(IActorRef target) => ReceiveAny(msg => target.Forward(msg));
    }

    private IActorRef CreateDeploymentManager() =>
        ActorOf(Props.Create(() => new DeploymentManagerActor(
            _storage, _compilationService, _sharedScriptLibrary,
            null, new SiteRuntimeOptions(), NullLogger<DeploymentManagerActor>.Instance)));

    [Fact]
    public void SiteNodeJoined_PushesLocalTrustedCertsToJoinedNode()
    {
        // Stand a forwarder at the well-known cert-store name so both the local
        // export Ask and the remote-path write land on our probe (self address in
        // this single-node test resolves the remote path back to the local actor).
        var certStoreProbe = CreateTestProbe();
        Sys.ActorOf(Props.Create(() => new ForwardingActor(certStoreProbe.Ref)), CertStoreActor.WellKnownName);

        var dm = CreateDeploymentManager();

        dm.Tell(new DeploymentManagerActor.SiteNodeJoined(Cluster.Get(Sys).SelfAddress));

        // 1) The singleton reads its own trusted store.
        certStoreProbe.ExpectMsg<ExportLocalTrustedCerts>();

        // 2) Feed the export back; the singleton pushes each cert to the joined node.
        dm.Tell(new LocalCertExport(true, null,
            new[] { new ExportedCert("ABC123", new byte[] { 1, 2, 3 }) }));

        var write = certStoreProbe.ExpectMsg<WriteCertToLocalStore>(TimeSpan.FromSeconds(5));
        Assert.Equal("ABC123", write.Thumbprint);
        Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3 }), write.DerBase64);
    }
}
