using Akka.Actor;
using Akka.TestKit;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations;
using ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests;

public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
{
    /// <summary>Verifies that starting a deployment inserts a row and dispatches to the coordinator.</summary>
    [Fact]
    public void StartDeployment_inserts_deployment_and_dispatches_to_coordinator()
    {
        var dbFactory = NewInMemoryDbFactory();
        var coordinator = CreateTestProbe("coord");
        var actor = Sys.ActorOf(AdminOperationsActor.Props(dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>()));

        actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));

        var dispatch = coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
        dispatch.DeploymentId.Value.ShouldNotBe(Guid.Empty);
        dispatch.RevisionHash.Value.Length.ShouldBe(64);

        var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
        reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
        reply.DeploymentId.ShouldBe(dispatch.DeploymentId);
        reply.RevisionHash.ShouldBe(dispatch.RevisionHash);

        using var db = dbFactory.CreateDbContext();
        var row = db.Deployments.Single();
        row.Status.ShouldBe(DeploymentStatus.Dispatching);
        row.CreatedBy.ShouldBe("joe");
        row.ArtifactBlob.Length.ShouldBeGreaterThan(0);

        db.ConfigEdits.Count().ShouldBe(1);
        db.ConfigEdits.Single().EntityType.ShouldBe("Deployment");
    }

    /// <summary>Verifies that starting a deployment is refused when another is in flight.</summary>
    [Fact]
    public void StartDeployment_refuses_when_another_is_in_flight()
    {
        var dbFactory = NewInMemoryDbFactory();
        // Seed an in-flight Deployment.
        using (var db = dbFactory.CreateDbContext())
        {
            db.Deployments.Add(new Configuration.Entities.Deployment
            {
                RevisionHash = new string('a', 64),
                Status = DeploymentStatus.Dispatching,
                CreatedBy = "earlier",
            });
            db.SaveChanges();
        }

        var coordinator = CreateTestProbe("coord");
        var actor = Sys.ActorOf(AdminOperationsActor.Props(dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>()));

        actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));

        coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
        var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
        reply.Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight);
        reply.DeploymentId.ShouldNotBeNull();
    }
}
