using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Infrastructure.Diagnostics;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;

public sealed class NativeOperatorLogTransitionTrackerTests
{
    [Fact]
    public void DatabaseMapping_CoversEveryClosedHealthState()
    {
        var expected = new Dictionary<DatabaseHealthState, NativeOperatorDatabaseState>
        {
            [DatabaseHealthState.NotConfigured] = NativeOperatorDatabaseState.NotConfigured,
            [DatabaseHealthState.Healthy] = NativeOperatorDatabaseState.Healthy,
            [DatabaseHealthState.Unhealthy] = NativeOperatorDatabaseState.Unhealthy,
            [DatabaseHealthState.TimedOut] = NativeOperatorDatabaseState.TimedOut
        };

        Assert.Equal(Enum.GetValues<DatabaseHealthState>().Length, expected.Count);
        foreach (var pair in expected)
        {
            Assert.True(NativeOperatorLogTransitionTracker.TryMapDatabaseState(
                pair.Key,
                out var actual));
            Assert.Equal(pair.Value, actual);
        }
        Assert.False(NativeOperatorLogTransitionTracker.TryMapDatabaseState(
            (DatabaseHealthState)int.MaxValue,
            out _));
    }

    [Fact]
    public void TornadoMapping_CoversEveryClosedConnectionState()
    {
        var expected = new Dictionary<PlayoutConnectionState, NativeOperatorTornadoState>
        {
            [PlayoutConnectionState.Disabled] = NativeOperatorTornadoState.Disabled,
            [PlayoutConnectionState.DryRunReady] = NativeOperatorTornadoState.DryRunReady,
            [PlayoutConnectionState.Disconnected] = NativeOperatorTornadoState.Disconnected,
            [PlayoutConnectionState.Connecting] = NativeOperatorTornadoState.Connecting,
            [PlayoutConnectionState.Connected] = NativeOperatorTornadoState.Connected,
            [PlayoutConnectionState.Reconnecting] = NativeOperatorTornadoState.Reconnecting,
            [PlayoutConnectionState.Disconnecting] = NativeOperatorTornadoState.Disconnecting,
            [PlayoutConnectionState.Faulted] = NativeOperatorTornadoState.Faulted,
            [PlayoutConnectionState.OutcomeUnknown] = NativeOperatorTornadoState.OutcomeUnknown,
            [PlayoutConnectionState.Disposed] = NativeOperatorTornadoState.Disposed
        };

        Assert.Equal(Enum.GetValues<PlayoutConnectionState>().Length, expected.Count);
        foreach (var pair in expected)
        {
            Assert.True(NativeOperatorLogTransitionTracker.TryMapTornadoState(
                pair.Key,
                out var actual));
            Assert.Equal(pair.Value, actual);
        }
        Assert.False(NativeOperatorLogTransitionTracker.TryMapTornadoState(
            (PlayoutConnectionState)int.MaxValue,
            out _));
    }

    [Fact]
    public void DatabaseTransitions_SuppressDuplicatesIndependentlyPerProvider()
    {
        var tracker = new NativeOperatorLogTransitionTracker();

        Assert.True(tracker.TryObserveDatabaseState(
            DataSourceKind.Oracle,
            DatabaseHealthState.Healthy,
            out var oracleHealthy));
        Assert.Equal(NativeOperatorLogEvent.OracleStateChanged, oracleHealthy.Event);
        Assert.Equal(NativeOperatorDatabaseState.Healthy, oracleHealthy.DatabaseState);
        Assert.False(tracker.TryObserveDatabaseState(
            DataSourceKind.Oracle,
            DatabaseHealthState.Healthy,
            out _));

        Assert.True(tracker.TryObserveDatabaseState(
            DataSourceKind.MariaDb,
            DatabaseHealthState.Healthy,
            out var mariaHealthy));
        Assert.Equal(NativeOperatorLogEvent.MariaDbStateChanged, mariaHealthy.Event);
        Assert.False(tracker.TryObserveDatabaseState(
            DataSourceKind.MariaDb,
            DatabaseHealthState.Healthy,
            out _));

        Assert.True(tracker.TryObserveDatabaseState(
            DataSourceKind.Oracle,
            DatabaseHealthState.TimedOut,
            out var oracleTimedOut));
        Assert.Equal(NativeOperatorDatabaseState.TimedOut, oracleTimedOut.DatabaseState);
        Assert.False(tracker.TryObserveDatabaseState(
            (DataSourceKind)int.MaxValue,
            DatabaseHealthState.Healthy,
            out _));
    }

    [Fact]
    public void TornadoTransitions_SuppressOnlyIdenticalConsecutiveStates()
    {
        var tracker = new NativeOperatorLogTransitionTracker();

        Assert.True(tracker.TryObserveTornadoState(
            PlayoutConnectionState.Disconnected,
            out var disconnected));
        Assert.Equal(NativeOperatorTornadoState.Disconnected, disconnected.TornadoState);
        Assert.False(tracker.TryObserveTornadoState(
            PlayoutConnectionState.Disconnected,
            out _));
        Assert.True(tracker.TryObserveTornadoState(
            PlayoutConnectionState.Connecting,
            out var connecting));
        Assert.Equal(NativeOperatorTornadoState.Connecting, connecting.TornadoState);
        Assert.True(tracker.TryObserveTornadoState(
            PlayoutConnectionState.Disconnected,
            out _));
    }

    [Fact]
    public void CommandTracker_EmitsOneRequestAndExactlyOneMatchingTerminalRecord()
    {
        var tracker = new NativeOperatorCommandLogTracker();

        Assert.True(tracker.TryBegin(
            NativeOperatorPlayoutCommand.Prepare,
            out var requested));
        Assert.Equal(NativeOperatorLogEvent.PlayoutCommandRequested, requested.Event);
        Assert.Equal(NativeOperatorPlayoutCommand.Prepare, requested.Command);
        Assert.False(tracker.TryComplete(
            NativeOperatorPlayoutCommand.Next,
            NativeOperatorPlayoutCompletion.Succeeded,
            out _));
        Assert.True(tracker.TryComplete(
            NativeOperatorPlayoutCommand.Prepare,
            NativeOperatorPlayoutCompletion.Rejected,
            out var completed));
        Assert.Equal(NativeOperatorLogEvent.PlayoutCommandCompleted, completed.Event);
        Assert.Equal(NativeOperatorPlayoutCompletion.Rejected, completed.Completion);
        Assert.False(tracker.TryComplete(
            NativeOperatorPlayoutCommand.Prepare,
            NativeOperatorPlayoutCompletion.Succeeded,
            out _));
        Assert.False(tracker.TryMarkCurrentOutcomeUnknown(out _));
    }

    [Fact]
    public void CommandTracker_TimeoutOutcomeWinsAndSuppressesLateCompletion()
    {
        var tracker = new NativeOperatorCommandLogTracker();
        Assert.True(tracker.TryBegin(NativeOperatorPlayoutCommand.TakeIn, out _));

        Assert.True(tracker.TryMarkCurrentOutcomeUnknown(out var unknown));
        Assert.Equal(NativeOperatorLogEvent.OutcomeUnknown, unknown.Event);
        Assert.Equal(NativeOperatorPlayoutCommand.TakeIn, unknown.Command);
        Assert.False(tracker.TryComplete(
            NativeOperatorPlayoutCommand.TakeIn,
            NativeOperatorPlayoutCompletion.Succeeded,
            out _));

        Assert.True(tracker.TryBegin(NativeOperatorPlayoutCommand.Next, out _));
        Assert.True(tracker.TryMarkOutcomeUnknown(
            NativeOperatorPlayoutCommand.Next,
            out var secondUnknown));
        Assert.Equal(NativeOperatorLogEvent.OutcomeUnknown, secondUnknown.Event);
    }

    [Fact]
    public void CommandTracker_UndefinedScalarsFailClosed()
    {
        var tracker = new NativeOperatorCommandLogTracker();

        Assert.False(tracker.TryBegin((NativeOperatorPlayoutCommand)int.MaxValue, out _));
        Assert.False(tracker.TryComplete(
            NativeOperatorPlayoutCommand.Prepare,
            (NativeOperatorPlayoutCompletion)int.MaxValue,
            out _));
        Assert.False(tracker.TryMarkOutcomeUnknown(
            (NativeOperatorPlayoutCommand)int.MaxValue,
            out _));
    }
}
