using Akka.Actor;
using Microsoft.EntityFrameworkCore;
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;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Configuration.Validation;
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;

/// <summary>
///     R2-11 (05/CONV-2) — the deploy gate's TagConfig strictness inspection, exercised THROUGH the actor
///     (the F10b/PR#423 lesson: an inspector built but never called is inert). Warn mode (default) accepts
///     the deploy with the warning surfaced in the result Message; Error mode rejects the same draft; a
///     clean config and an unmapped (Galaxy) tag produce no TagConfig noise.
/// </summary>
public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTestBase
{
    // v3: a raw Tag binds to a Device (Tag.DeviceId), and its driver type is resolved
    // Tag → Device → DriverInstance.DriverType. The tag-config gate inspects every raw tag against that
    // resolved driver type. All entity Names must be legal RawPath segments (DraftValidator RawNameInvalid).
    private static void Seed(IDbContextFactory<OtOpcUaConfigDbContext> dbFactory, string driverType, string tagConfig,
        string tagName = "flow")
    {
        using var db = dbFactory.CreateDbContext();
        db.DriverInstances.Add(new DriverInstance
        {
            DriverInstanceId = "d-1", ClusterId = "",
            Name = "drv", DriverType = driverType, DriverConfig = "{}",
        });
        db.Devices.Add(new Device
        {
            DeviceId = "dev-1", DriverInstanceId = "d-1", Name = "dev", DeviceConfig = "{}",
        });
        db.Tags.Add(new Tag
        {
            TagId = "tag-1", DeviceId = "dev-1",
            Name = tagName, DataType = "Int16", AccessLevel = TagAccessLevel.Read, TagConfig = tagConfig,
        });
        db.SaveChanges();
    }

    private const string TypoModbus = "{\"region\":\"HoldingRegisters\",\"address\":1,\"dataType\":\"Intt16\"}";
    private const string CleanModbus = "{\"region\":\"HoldingRegisters\",\"address\":1,\"dataType\":\"Int16\"}";

    [Fact]
    public void Warn_mode_accepts_and_surfaces_the_warning_in_the_message()
    {
        var dbFactory = NewInMemoryDbFactory();
        Seed(dbFactory, "Modbus", TypoModbus);
        var coordinator = CreateTestProbe("coord");
        var actor = Sys.ActorOf(AdminOperationsActor.Props(
            dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Warn));

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

        coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
        var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
        reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
        reply.Message.ShouldNotBeNull();
        reply.Message.ShouldContain("Intt16");
        reply.Message.ShouldContain("tag-1");
    }

    [Fact]
    public void Error_mode_rejects_the_same_draft()
    {
        var dbFactory = NewInMemoryDbFactory();
        Seed(dbFactory, "Modbus", TypoModbus);
        var coordinator = CreateTestProbe("coord");
        var actor = Sys.ActorOf(AdminOperationsActor.Props(
            dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Error));

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

        coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
        var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
        reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
        reply.Message.ShouldNotBeNull();
        reply.Message.ShouldContain("TagConfigInvalid");
        reply.Message.ShouldContain("Intt16");

        using var verify = dbFactory.CreateDbContext();
        verify.Deployments.Count().ShouldBe(0);
    }

    [Fact]
    public void Clean_config_accepts_with_no_tagconfig_noise()
    {
        var dbFactory = NewInMemoryDbFactory();
        Seed(dbFactory, "Modbus", CleanModbus);
        var coordinator = CreateTestProbe("coord");
        var actor = Sys.ActorOf(AdminOperationsActor.Props(
            dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Warn));

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

        coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
        var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
        reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
        (reply.Message is null || !reply.Message.Contains("Intt16")).ShouldBeTrue();
    }

    [Fact]
    public void Unmapped_galaxy_tag_is_skipped_even_in_error_mode()
    {
        var dbFactory = NewInMemoryDbFactory();
        // A Galaxy raw tag (v3: an ordinary Device-bound tag); the inspector has no Galaxy mapping, so it
        // is skipped regardless of mode — even with a nonsense dataType in its TagConfig.
        Seed(dbFactory, "GalaxyMxGateway", "{\"FullName\":\"Obj.Attr\",\"dataType\":\"nonsense\"}");
        var coordinator = CreateTestProbe("coord");
        var actor = Sys.ActorOf(AdminOperationsActor.Props(
            dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Error));

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

        coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
        ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3)).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
    }
}
