using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using MBN_STOCK_WEBVIEW.Playout.Interop;

namespace MBN_STOCK_WEBVIEW.Playout.Tests;

public sealed class DynamicK3dEventHandlerTests
{
    [Fact]
    public void GeneratedHandler_ImplementsImportedInterfaceAndForwardsOnlyLifecycleCallbacks()
    {
        var sink = new RecordingSink();
        var value = DynamicK3dEventHandlerBuilder.Create(typeof(IFakeK3dEventHandler), sink);
        var handler = Assert.IsAssignableFrom<IFakeK3dEventHandler>(value);

        handler.OnHello();
        handler.OnScenePlayed(1, 9, 10);
        handler.OnCutOut(0, 9, 10);
        handler.OnStopAll(1);
        handler.OnIgnored("sensitive value");

        Assert.Equal(
            new[]
            {
                "OnHello:",
                "OnScenePlayed:1,9,10",
                "OnCutOut:0,9,10",
                "OnStopAll:1"
            },
            sink.Calls);
        Assert.Equal(
            ClassInterfaceType.None,
            value.GetType().GetCustomAttributes(typeof(ClassInterfaceAttribute), inherit: false)
                .Cast<ClassInterfaceAttribute>()
                .Single()
                .Value);
    }

    [Fact]
    public void EventQueue_ParsesCallbacksWithoutThrowingAcrossNativeBoundary()
    {
        var queue = new K3dEventQueue(capacity: 4);

        queue.OnCallback("OnHello", []);
        queue.OnCallback("OnScenePlayed", [1, 9, 10]);
        queue.OnCallback("OnCutOut", [0, 9, 10]);
        queue.OnCallback("OnStopAll", [1]);
        queue.OnCallback("OnScenePlayed", ["invalid"]);

        Assert.True(queue.HelloObserved);
        Assert.False(queue.HasOverflowed);
        Assert.Equal(3, queue.PendingCount);
        Assert.True(queue.TryDequeue(out var played));
        Assert.Equal(
            new K3dLifecycleCallback(K3dLifecycleCallbackKind.ScenePlayed, true, 9, 10),
            played);
        Assert.True(queue.TryDequeue(out var cut));
        Assert.False(cut.IsSuccess);
        Assert.True(queue.TryDequeue(out var stopped));
        Assert.Equal(K3dLifecycleCallbackKind.StopAll, stopped.Kind);
        Assert.False(queue.TryDequeue(out _));
    }

    [Fact]
    public void EventQueue_OverflowLatchesAndNeverExceedsCapacity()
    {
        var queue = new K3dEventQueue(capacity: 1);

        queue.OnCallback("OnStopAll", [1]);
        queue.OnCallback("OnStopAll", [1]);

        Assert.True(queue.HasOverflowed);
        Assert.Equal(1, queue.PendingCount);
        Assert.True(queue.TryDequeue(out _));
        Assert.False(queue.TryDequeue(out _));
    }

    private sealed class RecordingSink : IK3dEventSink
    {
        private readonly ConcurrentQueue<string> _calls = new();

        public string[] Calls => _calls.ToArray();

        public void OnCallback(string methodName, object?[] arguments) =>
            _calls.Enqueue($"{methodName}:{string.Join(',', arguments)}");
    }

    [ComImport]
    [Guid("C21817DB-C0D3-4647-8D85-027338DCF832")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IFakeK3dEventHandler
    {
        void OnHello();

        void OnScenePlayed(int success, int outputChannelIndex, int layerIndex);

        void OnCutOut(int success, int outputChannelIndex, int layerIndex);

        void OnStopAll(int success);

        void OnIgnored(string value);
    }
}
