using Akka.Actor;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
using ZB.MOM.WW.ScadaBridge.SiteRuntime;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;

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

/// <summary>
/// Task 15: NativeAlarmActor mirrors a source's native alarms — subscribe on start,
/// emit enriched AlarmStateChanged, snapshot atomic swap, retention, persistence.
/// </summary>
public class NativeAlarmActorTests : TestKit, IDisposable
{
    private readonly string _dbFile;
    private readonly SiteStorageService _storage;
    private readonly SiteRuntimeOptions _options = new();

    public NativeAlarmActorTests()
    {
        _dbFile = Path.Combine(Path.GetTempPath(), $"naa-{Guid.NewGuid():N}.db");
        _storage = new SiteStorageService($"Data Source={_dbFile}", NullLogger<SiteStorageService>.Instance);
        _storage.InitializeAsync().GetAwaiter().GetResult();
    }

    private static ResolvedNativeAlarmSource Source() => new()
    {
        CanonicalName = "Pressure",
        ConnectionName = "Opc",
        SourceReference = "ns=2;s=T01"
    };

    private static NativeAlarmTransition Transition(
        string sourceRef, AlarmTransitionKind kind, AlarmConditionState condition, DateTimeOffset? time = null) =>
        new(sourceRef, "T01", "AnalogLimit.Hi", kind, condition,
            "Process", "hi", "hi", "", "", null, time ?? DateTimeOffset.UtcNow, "92", "90");

    private IActorRef Spawn(IActorRef instanceActor, IActorRef dclManager) =>
        ActorOf(Props.Create(() => new NativeAlarmActor(
            Source(), "inst", instanceActor, dclManager, _storage, _options, NullLogger<NativeAlarmActor>.Instance)));

    [Fact]
    public void SubscribeOnStart_SendsRequestForSourceBinding()
    {
        var dcl = CreateTestProbe();
        Spawn(CreateTestProbe().Ref, dcl.Ref);

        var req = dcl.ExpectMsg<SubscribeAlarmsRequest>();
        Assert.Equal("inst", req.InstanceUniqueName);
        Assert.Equal("Opc", req.ConnectionName);
        Assert.Equal("ns=2;s=T01", req.SourceReference);
    }

    [Fact]
    public void Raise_EmitsEnrichedAlarmStateChanged()
    {
        var instance = CreateTestProbe();
        var dcl = CreateTestProbe();
        var actor = Spawn(instance.Ref, dcl.Ref);
        dcl.ExpectMsg<SubscribeAlarmsRequest>();

        actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
            "T01.Hi", AlarmTransitionKind.Raise,
            new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 800))));

        var emitted = instance.ExpectMsg<AlarmStateChanged>();
        Assert.Equal(AlarmKind.NativeOpcUa, emitted.Kind);
        Assert.Equal("T01.Hi", emitted.SourceReference);
        Assert.Equal(AlarmState.Active, emitted.State);
        Assert.Equal(800, emitted.Condition.Severity);
    }

    [Fact]
    public void SnapshotComplete_WithMissingPriorAlarm_EmitsReturnToNormal()
    {
        var instance = CreateTestProbe();
        var dcl = CreateTestProbe();
        var actor = Spawn(instance.Ref, dcl.Ref);
        dcl.ExpectMsg<SubscribeAlarmsRequest>();

        actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
            "T01.Hi", AlarmTransitionKind.Raise,
            new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 800))));
        instance.ExpectMsg<AlarmStateChanged>(m => m.State == AlarmState.Active);

        // A fresh snapshot that no longer contains T01.Hi means it cleared at the source.
        actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
            "T01.Hi", AlarmTransitionKind.SnapshotComplete,
            new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 800))));

        var cleared = instance.ExpectMsg<AlarmStateChanged>();
        Assert.Equal("T01.Hi", cleared.SourceReference);
        Assert.Equal(AlarmState.Normal, cleared.State);
    }

    [Fact]
    public void OlderTransition_IsIgnored()
    {
        var instance = CreateTestProbe();
        var dcl = CreateTestProbe();
        var actor = Spawn(instance.Ref, dcl.Ref);
        dcl.ExpectMsg<SubscribeAlarmsRequest>();

        var t0 = DateTimeOffset.UtcNow;
        actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
            "T01.Hi", AlarmTransitionKind.Raise,
            new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 800), t0)));
        instance.ExpectMsg<AlarmStateChanged>(m => m.State == AlarmState.Active);

        // An out-of-order (older) transition must not overwrite newer state.
        actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
            "T01.Hi", AlarmTransitionKind.Clear,
            new AlarmConditionState(false, true, null, AlarmShelveState.Unshelved, false, 0), t0.AddSeconds(-30))));

        instance.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
    }

    void IDisposable.Dispose()
    {
        Shutdown();
        if (File.Exists(_dbFile))
        {
            File.Delete(_dbFile);
        }
    }
}
