using System;
using System.Collections.Generic;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Worker.MxAccess;
using ZB.MOM.WW.MxGateway.Worker.Sta;
using ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport;

namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess;

/// <summary>
///     Verifies that the four new alarm <see cref="MxCommandKind"/> values
///     route through <see cref="MxAccessCommandExecutor"/> to a fake
///     <see cref="IAlarmCommandHandler"/> and that the resulting
///     <see cref="MxCommandReply"/> carries the expected payload.
///
///     The data-side <see cref="MxAccessSession"/> is constructed via a
///     no-op factory because the executor only touches it for non-alarm
///     command kinds — alarm dispatch never reaches the data session.
/// </summary>
public sealed class AlarmCommandExecutorTests
{
    private const string SessionId = "S";
    private const string CorrelationId = "C";

    [Fact]
    public void SubscribeAlarms_WithHandler_RoutesToHandlerAndReturnsOk()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler();
        MxAccessCommandExecutor executor = NewExecutor(handler);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.SubscribeAlarms,
                SubscribeAlarms = new SubscribeAlarmsCommand
                {
                    SubscriptionExpression = @"\\HOST\Galaxy!Area",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
        Assert.Equal(@"\\HOST\Galaxy!Area", handler.LastSubscription);
        Assert.Equal(SessionId, handler.LastSessionId);
    }

    [Fact]
    public void SubscribeAlarms_WithoutHandler_ReturnsInvalidRequest()
    {
        MxAccessCommandExecutor executor = NewExecutor(alarmHandler: null);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.SubscribeAlarms,
                SubscribeAlarms = new SubscribeAlarmsCommand
                {
                    SubscriptionExpression = @"\\HOST\Galaxy!Area",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
    }

    [Fact]
    public void SubscribeAlarms_WithEmptyExpression_ReturnsInvalidRequest()
    {
        MxAccessCommandExecutor executor = NewExecutor(new FakeAlarmHandler());

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.SubscribeAlarms,
                SubscribeAlarms = new SubscribeAlarmsCommand
                {
                    SubscriptionExpression = "  ",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
    }

    [Fact]
    public void AcknowledgeAlarm_WithHandler_RoutesNativeStatusIntoHresultAndPayload()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler { AcknowledgeReturn = 0 };
        MxAccessCommandExecutor executor = NewExecutor(handler);
        Guid g = Guid.NewGuid();

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.AcknowledgeAlarm,
                AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
                {
                    AlarmGuid = g.ToString(),
                    Comment = "ack",
                    OperatorUser = "alice",
                    OperatorNode = "WS",
                    OperatorDomain = "CORP",
                    OperatorFullName = "Alice S",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
        Assert.Equal(0, reply.Hresult);
        Assert.NotNull(reply.AcknowledgeAlarm);
        Assert.Equal(0, reply.AcknowledgeAlarm.NativeStatus);
        Assert.Equal(g, handler.LastAckGuid);
        Assert.Equal("alice", handler.LastAckOperatorName);
    }

    [Fact]
    public void AcknowledgeAlarm_WithInvalidGuid_ReturnsInvalidRequest()
    {
        MxAccessCommandExecutor executor = NewExecutor(new FakeAlarmHandler());

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.AcknowledgeAlarm,
                AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
                {
                    AlarmGuid = "not-a-guid",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
    }

    [Fact]
    public void AcknowledgeAlarm_WithNonzeroNativeStatus_CarriesDiagnostic()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler { AcknowledgeReturn = -123 };
        MxAccessCommandExecutor executor = NewExecutor(handler);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.AcknowledgeAlarm,
                AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
                {
                    AlarmGuid = Guid.NewGuid().ToString(),
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(-123, reply.Hresult);
        Assert.Contains("-123", reply.DiagnosticMessage);
    }

    [Fact]
    public void AcknowledgeAlarmByName_WithHandler_RoutesTupleToHandler()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler { AcknowledgeReturn = 0 };
        MxAccessCommandExecutor executor = NewExecutor(handler);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.AcknowledgeAlarmByName,
                AcknowledgeAlarmByNameCommand = new AcknowledgeAlarmByNameCommand
                {
                    AlarmName = "TestMachine_001.TestAlarm001",
                    ProviderName = "Galaxy",
                    GroupName = "TestArea",
                    Comment = "ack",
                    OperatorUser = "alice",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
        Assert.NotNull(reply.AcknowledgeAlarm);
        Assert.Equal(0, reply.AcknowledgeAlarm.NativeStatus);
        Assert.NotNull(handler.LastAckByNameTuple);
        Assert.Equal("TestMachine_001.TestAlarm001", handler.LastAckByNameTuple!.Value.Name);
        Assert.Equal("Galaxy", handler.LastAckByNameTuple!.Value.Provider);
        Assert.Equal("TestArea", handler.LastAckByNameTuple!.Value.Group);
        Assert.Equal("alice", handler.LastAckOperatorName);
    }

    [Fact]
    public void AcknowledgeAlarmByName_WithEmptyName_ReturnsInvalidRequest()
    {
        MxAccessCommandExecutor executor = NewExecutor(new FakeAlarmHandler());

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.AcknowledgeAlarmByName,
                AcknowledgeAlarmByNameCommand = new AcknowledgeAlarmByNameCommand
                {
                    AlarmName = " ",
                    ProviderName = "Galaxy",
                    GroupName = "TestArea",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
    }

    [Fact]
    public void QueryActiveAlarms_WithHandler_ReturnsPayloadWithSnapshots()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler
        {
            QueryResult = new[]
            {
                new ActiveAlarmSnapshot { AlarmFullReference = "Galaxy!A.T1" },
                new ActiveAlarmSnapshot { AlarmFullReference = "Galaxy!A.T2" },
            },
        };
        MxAccessCommandExecutor executor = NewExecutor(handler);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.QueryActiveAlarms,
                QueryActiveAlarmsCommand = new QueryActiveAlarmsCommand
                {
                    AlarmFilterPrefix = "Galaxy!A",
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
        Assert.NotNull(reply.QueryActiveAlarms);
        Assert.Equal(2, reply.QueryActiveAlarms.Snapshots.Count);
        Assert.Equal("Galaxy!A", handler.LastFilterPrefix);
    }

    [Fact]
    public void UnsubscribeAlarms_WithHandler_RoutesToHandler()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler();
        MxAccessCommandExecutor executor = NewExecutor(handler);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.UnsubscribeAlarms,
                UnsubscribeAlarms = new UnsubscribeAlarmsCommand(),
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
        Assert.True(handler.UnsubscribeCalled);
    }

    [Fact]
    public void UnsubscribeAlarms_WithoutHandler_IsOkNoop()
    {
        MxAccessCommandExecutor executor = NewExecutor(alarmHandler: null);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.UnsubscribeAlarms,
                UnsubscribeAlarms = new UnsubscribeAlarmsCommand(),
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
    }

    [Fact]
    public void AcknowledgeAlarm_WhenHandlerThrows_ReturnsMxaccessFailure()
    {
        FakeAlarmHandler handler = new FakeAlarmHandler { AcknowledgeThrow = true };
        MxAccessCommandExecutor executor = NewExecutor(handler);

        StaCommand command = new StaCommand(
            SessionId, CorrelationId,
            new MxCommand
            {
                Kind = MxCommandKind.AcknowledgeAlarm,
                AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
                {
                    AlarmGuid = Guid.NewGuid().ToString(),
                },
            });

        MxCommandReply reply = executor.Execute(command);

        Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
        Assert.Contains("simulated", reply.DiagnosticMessage);
    }

    private static MxAccessCommandExecutor NewExecutor(IAlarmCommandHandler? alarmHandler)
    {
        // Construct an executor with a no-op data session — we only exercise
        // the alarm switch arms, which never touch the data session. The
        // session is built through the internal MxAccessSession.CreateForTesting
        // factory (exposed via [assembly: InternalsVisibleTo("ZB.MOM.WW.MxGateway.Worker.Tests")]
        // on ZB.MOM.WW.MxGateway.Worker), so no reflection is needed.
        return new MxAccessCommandExecutor(
            session: MxAccessSession.CreateForTesting(
                mxAccessServer: new NoopMxAccessServer(),
                eventSink: new NoopEventSink()),
            variantConverter: new ZB.MOM.WW.MxGateway.Worker.Conversion.VariantConverter(),
            alarmCommandHandler: alarmHandler);
    }

    private sealed class FakeAlarmHandler : IAlarmCommandHandler
    {
        public string? LastSubscription { get; private set; }
        public string? LastSessionId { get; private set; }
        public bool UnsubscribeCalled { get; private set; }
        public Guid LastAckGuid { get; private set; }
        public string? LastAckOperatorName { get; private set; }
        public int AcknowledgeReturn { get; set; }
        public bool AcknowledgeThrow { get; set; }
        public IReadOnlyList<ActiveAlarmSnapshot> QueryResult { get; set; } =
            Array.Empty<ActiveAlarmSnapshot>();
        public string? LastFilterPrefix { get; private set; }

        public void Subscribe(string subscription, string sessionId)
        {
            LastSubscription = subscription;
            LastSessionId = sessionId;
        }

        public void Unsubscribe()
        {
            UnsubscribeCalled = true;
        }

        public int Acknowledge(
            Guid alarmGuid, string comment, string operatorUser,
            string operatorNode, string operatorDomain, string operatorFullName)
        {
            LastAckGuid = alarmGuid;
            LastAckOperatorName = operatorUser;
            if (AcknowledgeThrow)
            {
                throw new InvalidOperationException("simulated alarm-handler failure");
            }
            return AcknowledgeReturn;
        }

        public int AcknowledgeByName(
            string alarmName, string providerName, string groupName,
            string comment, string operatorUser, string operatorNode,
            string operatorDomain, string operatorFullName)
        {
            LastAckByNameTuple = (alarmName, providerName, groupName);
            LastAckOperatorName = operatorUser;
            return AcknowledgeReturn;
        }

        public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }

        public IReadOnlyList<ActiveAlarmSnapshot> QueryActive(string? alarmFilterPrefix)
        {
            LastFilterPrefix = alarmFilterPrefix;
            return QueryResult;
        }

        public int PollCount { get; private set; }

        public void PollOnce()
        {
            PollCount++;
        }

        public void Dispose() { }
    }
}
