using System;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Worker.MxAccess;

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

/// <summary>
///     Pins the pure helpers used to translate AVEVA's wnwrapConsumer XML
///     payloads into proto-friendly fields. The COM-side I/O on
///     <see cref="WnWrapAlarmConsumer"/> needs an AVEVA install and is
///     covered by the Skip-gated probe (<c>WnWrapConsumerProbeTests</c>);
///     these unit tests cover everything that doesn't touch the live COM
///     surface.
/// </summary>
public sealed class AlarmRecordTransitionMapperTests
{
    /// <summary>Verifies that full alarm reference uses provider!group.name format.</summary>
    [Fact]
    public void ComposeFullReference_WithProviderAndGroup_UsesProviderBangGroupDotNameFormat()
    {
        string reference = AlarmRecordTransitionMapper.ComposeFullReference(
            providerName: "GalaxyAlarmProvider",
            groupName: "Tank01",
            alarmName: "Level.HiHi");
        Assert.Equal("GalaxyAlarmProvider!Tank01.Level.HiHi", reference);
    }

    /// <summary>Verifies that reference omits provider when empty.</summary>
    [Fact]
    public void ComposeFullReference_WithEmptyProvider_DropsProvider()
    {
        string reference = AlarmRecordTransitionMapper.ComposeFullReference(
            providerName: null, groupName: "Tank01", alarmName: "Level.HiHi");
        Assert.Equal("Tank01.Level.HiHi", reference);
    }

    /// <summary>Verifies that reference omits group when empty.</summary>
    [Fact]
    public void ComposeFullReference_WithEmptyGroup_DropsGroup()
    {
        string reference = AlarmRecordTransitionMapper.ComposeFullReference(
            providerName: "GalaxyAlarmProvider", groupName: null, alarmName: "GlobalAlarm");
        Assert.Equal("GalaxyAlarmProvider!GlobalAlarm", reference);
    }

    /// <summary>Verifies that reference returns alarm name when provider and group are empty.</summary>
    [Fact]
    public void ComposeFullReference_WithEmptyProviderAndGroup_ReturnsAlarmName()
    {
        string reference = AlarmRecordTransitionMapper.ComposeFullReference(
            providerName: null, groupName: null, alarmName: "Bare");
        Assert.Equal("Bare", reference);
    }

    /// <summary>Verifies that state string parsing decodes all valid state kinds.</summary>
    /// <param name="input">The state string to parse.</param>
    /// <param name="expected">The expected decoded state kind.</param>
    [Theory]
    [InlineData("UNACK_ALM", MxAlarmStateKind.UnackAlm)]
    [InlineData("ACK_ALM", MxAlarmStateKind.AckAlm)]
    [InlineData("UNACK_RTN", MxAlarmStateKind.UnackRtn)]
    [InlineData("ACK_RTN", MxAlarmStateKind.AckRtn)]
    [InlineData("unack_alm", MxAlarmStateKind.UnackAlm)] // case-insensitive
    [InlineData("  ACK_ALM  ", MxAlarmStateKind.AckAlm)] // trim
    [InlineData("UNKNOWN", MxAlarmStateKind.Unspecified)]
    [InlineData("", MxAlarmStateKind.Unspecified)]
    [InlineData(null, MxAlarmStateKind.Unspecified)]
    public void ParseStateKind_ForEachStateString_DecodesStateKind(string? input, MxAlarmStateKind expected)
    {
        Assert.Equal(expected, AlarmRecordTransitionMapper.ParseStateKind(input));
    }

    /// <summary>Verifies that state pair mapping decides the correct transition kind for all pairs.</summary>
    /// <param name="previous">The previous alarm state kind.</param>
    /// <param name="current">The current alarm state kind.</param>
    /// <param name="expected">The expected transition kind.</param>
    [Theory]
    // First sighting: new alarm in *_ALM → Raise.
    [InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Raise)]
    [InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.AckAlm, AlarmTransitionKind.Raise)]
    // First sighting in *_RTN → Clear (unusual; missed the original raise).
    [InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.UnackRtn, AlarmTransitionKind.Clear)]
    [InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.AckRtn, AlarmTransitionKind.Clear)]
    // Active → Cleared.
    [InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.UnackRtn, AlarmTransitionKind.Clear)]
    [InlineData(MxAlarmStateKind.AckAlm, MxAlarmStateKind.AckRtn, AlarmTransitionKind.Clear)]
    // Cleared → Active (re-trigger).
    [InlineData(MxAlarmStateKind.UnackRtn, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Raise)]
    [InlineData(MxAlarmStateKind.AckRtn, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Raise)]
    // Unacked → Acked (operator ack).
    [InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.AckAlm, AlarmTransitionKind.Acknowledge)]
    [InlineData(MxAlarmStateKind.UnackRtn, MxAlarmStateKind.AckRtn, AlarmTransitionKind.Acknowledge)]
    // No-op (state unchanged) — caller is supposed to filter these out.
    [InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Unspecified)]
    // Current=Unspecified → Unspecified.
    [InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.Unspecified, AlarmTransitionKind.Unspecified)]
    public void MapTransition_ForEachStatePair_DecidesProtoKind(
        MxAlarmStateKind previous,
        MxAlarmStateKind current,
        AlarmTransitionKind expected)
    {
        Assert.Equal(expected, AlarmRecordTransitionMapper.MapTransition(previous, current));
    }

    /// <summary>Verifies that valid XML fields assemble into correct UTC timestamp.</summary>
    [Fact]
    public void ParseTransitionTimestampUtc_WithValidXmlFields_AssemblesUtc()
    {
        // Captured payload from probe (2026-05-01): EDT producer, GMTOFFSET=240, DSTADJUST=0.
        // Local 13:26:14.709 + 240 minutes (4h) = 17:26:14.709 UTC.
        DateTime utc = AlarmRecordTransitionMapper.ParseTransitionTimestampUtc(
            "2026/5/1", "13:26:14.709", gmtOffsetMinutes: 240, dstAdjustMinutes: 0);

        Assert.Equal(DateTimeKind.Utc, utc.Kind);
        Assert.Equal(2026, utc.Year);
        Assert.Equal(5, utc.Month);
        Assert.Equal(1, utc.Day);
        Assert.Equal(17, utc.Hour);
        Assert.Equal(26, utc.Minute);
        Assert.Equal(14, utc.Second);
        Assert.Equal(709, utc.Millisecond);
    }

    /// <summary>Verifies that unparseable inputs return DateTime.MinValue.</summary>
    [Fact]
    public void ParseTransitionTimestampUtc_WithUnparseableInputs_ReturnsMinValue()
    {
        Assert.Equal(DateTime.MinValue,
            AlarmRecordTransitionMapper.ParseTransitionTimestampUtc(null, null, 0, 0));
        Assert.Equal(DateTime.MinValue,
            AlarmRecordTransitionMapper.ParseTransitionTimestampUtc("not a date", "13:00:00", 0, 0));
        Assert.Equal(DateTime.MinValue,
            AlarmRecordTransitionMapper.ParseTransitionTimestampUtc("2026/5/1", "not a time", 0, 0));
    }
}
