using System.Text.Json;
using Akka.Actor;
using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Fleet;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;

namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;

/// <summary>
///     Verifies the live-deploy wiring of <see cref="VirtualTagHostActor"/>: the
///     <see cref="DriverHostActor"/> must forward the composition's
///     <c>EquipmentVirtualTags</c> to its spawned VirtualTag host via
///     <see cref="VirtualTagHostActor.ApplyVirtualTags"/> on BOTH the fresh-apply path and the
///     bootstrap-restore path (both route through <c>PushDesiredSubscriptions</c>). The host is
///     injected as a <see cref="Akka.TestKit.TestProbe"/> via the Props override seam so the
///     ApplyVirtualTags can be intercepted.
/// </summary>
public sealed class DriverHostActorVirtualTagTests : RuntimeActorTestBase
{
    private static readonly NodeId TestNode = NodeId.Parse("driver-vt-test");
    private static readonly RevisionHash RevA = RevisionHash.Parse(new string('a', 64));

    /// <summary>Fresh apply: dispatching a deployment whose artifact carries one Equipment
    /// VirtualTag forwards an <see cref="VirtualTagHostActor.ApplyVirtualTags"/> carrying that
    /// plan to the injected VirtualTag host.</summary>
    [Fact]
    public void Apply_forwards_EquipmentVirtualTags_to_virtual_tag_host()
    {
        var db = NewInMemoryDbFactory();
        var deploymentId = SeedDeploymentWithVirtualTag(db, RevA);

        var coordinator = CreateTestProbe();
        var vtHost = CreateTestProbe();
        var actor = Sys.ActorOf(DriverHostActor.Props(
            db, TestNode, coordinator.Ref,
            localRoles: new HashSet<string> { "driver" },
            virtualTagEvaluator: NullVirtualTagEvaluator.Instance,
            virtualTagHostOverride: vtHost.Ref));

        actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));

        coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);

        var apply = vtHost.ExpectMsg<VirtualTagHostActor.ApplyVirtualTags>(TimeSpan.FromSeconds(5));
        var plan = apply.Plans.ShouldHaveSingleItem();
        plan.VirtualTagId.ShouldBe("vt-1");
        plan.EquipmentId.ShouldBe("eq-1");
        plan.Name.ShouldBe("Doubled");
    }

    /// <summary>Bootstrap-restore: a node that already has an <c>Applied</c> NodeDeploymentState
    /// row for a VirtualTag-carrying deployment re-forwards the
    /// <see cref="VirtualTagHostActor.ApplyVirtualTags"/> on PreStart (no dispatch needed), so a
    /// restarted node restores its live VirtualTag children.</summary>
    [Fact]
    public void Restore_on_bootstrap_forwards_EquipmentVirtualTags_to_virtual_tag_host()
    {
        var db = NewInMemoryDbFactory();
        var deploymentId = SeedDeploymentWithVirtualTag(db, RevA);
        SeedAppliedNodeState(db, deploymentId);

        var coordinator = CreateTestProbe();
        var vtHost = CreateTestProbe();
        // No DispatchDeployment — Bootstrap() should detect the Applied row and run RestoreApplied,
        // which routes through PushDesiredSubscriptions and forwards ApplyVirtualTags.
        Sys.ActorOf(DriverHostActor.Props(
            db, TestNode, coordinator.Ref,
            localRoles: new HashSet<string> { "driver" },
            virtualTagEvaluator: NullVirtualTagEvaluator.Instance,
            virtualTagHostOverride: vtHost.Ref));

        var apply = vtHost.ExpectMsg<VirtualTagHostActor.ApplyVirtualTags>(TimeSpan.FromSeconds(5));
        apply.Plans.ShouldHaveSingleItem().VirtualTagId.ShouldBe("vt-1");
    }

    private static DeploymentId SeedDeploymentWithVirtualTag(
        IDbContextFactory<OtOpcUaConfigDbContext> db, RevisionHash rev)
    {
        var artifact = JsonSerializer.SerializeToUtf8Bytes(new
        {
            Scripts = new[]
            {
                new
                {
                    ScriptId = "scr-1",
                    SourceCode = "return ctx.GetTag(\"TestMachine_001.TestDouble\").Value;",
                },
            },
            VirtualTags = new[]
            {
                new
                {
                    VirtualTagId = "vt-1",
                    EquipmentId = "eq-1",
                    Name = "Doubled",
                    DataType = "Float",
                    ScriptId = "scr-1",
                },
            },
        });

        var id = DeploymentId.NewId();
        using var ctx = db.CreateDbContext();
        ctx.Deployments.Add(new Deployment
        {
            DeploymentId = id.Value,
            RevisionHash = rev.Value,
            Status = DeploymentStatus.Sealed,
            CreatedBy = "test",
            SealedAtUtc = DateTime.UtcNow,
            ArtifactBlob = artifact,
        });
        ctx.SaveChanges();
        return id;
    }

    private static void SeedAppliedNodeState(
        IDbContextFactory<OtOpcUaConfigDbContext> db, DeploymentId deploymentId)
    {
        using var ctx = db.CreateDbContext();
        ctx.NodeDeploymentStates.Add(new NodeDeploymentState
        {
            NodeId = TestNode.Value,
            DeploymentId = deploymentId.Value,
            Status = NodeDeploymentStatus.Applied,
            StartedAtUtc = DateTime.UtcNow,
            AppliedAtUtc = DateTime.UtcNow,
        });
        ctx.SaveChanges();
    }
}
