using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using MBN_STOCK_WEBVIEW.Playout.Configuration;
using MBN_STOCK_WEBVIEW.Playout.Interop;
using MBN_STOCK_WEBVIEW.Playout.Registration;
using MBN_STOCK_WEBVIEW.Playout.Runtime;
using MBN_STOCK_WEBVIEW.Playout.Safety;

namespace MBN_STOCK_WEBVIEW.Playout.Tests;

internal sealed class FakeK3dSessionFactory : IK3dSessionFactory
{
    private readonly Func<FakeK3dSession> _create;
    private int _createCount;

    public FakeK3dSessionFactory(Func<FakeK3dSession>? create = null)
    {
        _create = create ?? (() => new FakeK3dSession());
    }

    public int CreateCount => Volatile.Read(ref _createCount);

    public ConcurrentQueue<FakeK3dSession> Sessions { get; } = new();

    public IK3dSession Create()
    {
        Interlocked.Increment(ref _createCount);
        var session = _create();
        Sessions.Enqueue(session);
        return session;
    }
}

internal sealed class FakeK3dSdkDispatchLatch(bool allow = true) : IK3dSdkDispatchLatch
{
    public IDisposable? TryClaim() => allow ? new Claim() : null;

    private sealed class Claim : IDisposable
    {
        public void Dispose()
        {
        }
    }
}

internal sealed class FakeK3dSession : IK3dGuardedConnectSession
{
    private int _disposed;
    private int _ktapDispatchGate;
    private int _ktapDispatchCount;

    public bool IsConnected { get; private set; }

    public PlayoutKtapConnectState LastKtapConnectState { get; private set; }

    public bool SupportsLifecycleCallbacks { get; set; }

    public bool? KtapHelloObserved { get; set; }

    public bool HasPendingLifecycleCallbacks { get; set; }

    public bool HasPendingPlayCallbacks { get; set; }

    public bool HasPendingTakeOutCallbacks { get; set; }

    public bool ClearPendingLifecycleCallbacksAfterProcess { get; set; } = true;

    public ConcurrentQueue<string> Calls { get; } = new();

    public ConcurrentQueue<int> ThreadIds { get; } = new();

    public ConcurrentQueue<ApartmentState> ApartmentStates { get; } = new();

    public Action<ValidatedPlayoutOptions>? ConnectAction { get; set; }

    public Action? BeforeKtapDispatchAction { get; set; }

    public int KtapDispatchCount => Volatile.Read(ref _ktapDispatchCount);

    public Action? DisconnectAction { get; set; }

    public Action<PlayoutCue, int>? PrepareAction { get; set; }

    public Action<PlayoutCue, int>? UpdateOnAirAction { get; set; }

    public Action<int>? PlayAction { get; set; }

    public Action<int, PlayoutTakeOutScope>? TakeOutAction { get; set; }

    public Func<int, K3dCallbackDrainResult>? ProcessPendingCallbacksAction { get; set; }

    public Action? AbandonAction { get; set; }

    public Action<string>? CallObserver { get; set; }

    public void Connect(ValidatedPlayoutOptions options)
        => ConnectCore(options, finalSafetyCheck: null);

    public void ConnectGuarded(
        ValidatedPlayoutOptions options,
        Func<bool> finalSafetyCheck,
        IK3dSdkDispatchLatch dispatchLatch)
    {
        ArgumentNullException.ThrowIfNull(finalSafetyCheck);
        ArgumentNullException.ThrowIfNull(dispatchLatch);
        ConnectCore(options, finalSafetyCheck, dispatchLatch);
    }

    private void ConnectCore(
        ValidatedPlayoutOptions options,
        Func<bool>? finalSafetyCheck,
        IK3dSdkDispatchLatch? dispatchLatch = null)
    {
        Record("Connect");
        BeforeKtapDispatchAction?.Invoke();
        if (finalSafetyCheck is not null && !finalSafetyCheck())
        {
            throw new K3dConnectSafetyException();
        }

        using (var dispatchClaim = dispatchLatch?.TryClaim())
        {
            if (dispatchLatch is not null && dispatchClaim is null)
            {
                throw new K3dConnectSafetyException();
            }

            if (Interlocked.CompareExchange(ref _ktapDispatchGate, 1, 0) != 0)
            {
                throw new InvalidOperationException("Fake KTAP dispatch was prevented.");
            }

            LastKtapConnectState = PlayoutKtapConnectState.Attempted;
            Interlocked.Increment(ref _ktapDispatchCount);
            try
            {
                ConnectAction?.Invoke(options);
            }
            catch
            {
                LastKtapConnectState = PlayoutKtapConnectState.Failed;
                throw;
            }
        }

        LastKtapConnectState = PlayoutKtapConnectState.AcceptedUnconfirmed;
        IsConnected = true;
    }

    public bool TryPreventKtapConnect()
    {
        var previous = Interlocked.CompareExchange(ref _ktapDispatchGate, 2, 0);
        if (previous == 1 && LastKtapConnectState == PlayoutKtapConnectState.NotAttempted)
        {
            LastKtapConnectState = PlayoutKtapConnectState.Attempted;
        }

        return previous == 0;
    }

    public void Disconnect()
    {
        Record("Disconnect");
        DisconnectAction?.Invoke();
        IsConnected = false;
    }

    public void Prepare(PlayoutCue cue, int layoutIndex)
    {
        Record($"Prepare:{cue.SceneName}");
        PrepareAction?.Invoke(cue, layoutIndex);
    }

    public void UpdateOnAir(PlayoutCue cue, int layoutIndex)
    {
        Record($"UpdateOnAir:{cue.SceneName}");
        UpdateOnAirAction?.Invoke(cue, layoutIndex);
    }

    public void Play(int layoutIndex)
    {
        Record("Play");
        PlayAction?.Invoke(layoutIndex);
    }

    public void TakeOut(int layoutIndex, PlayoutTakeOutScope scope)
    {
        Record($"TakeOut:{scope}");
        TakeOutAction?.Invoke(layoutIndex, scope);
    }

    public K3dCallbackDrainResult ProcessPendingCallbacks(int layoutIndex)
    {
        Record("ProcessPendingCallbacks");
        var result = ProcessPendingCallbacksAction?.Invoke(layoutIndex)
            ?? new K3dCallbackDrainResult(0, 0, KtapHelloObserved == true);
        if (ClearPendingLifecycleCallbacksAfterProcess)
        {
            HasPendingLifecycleCallbacks = false;
            HasPendingPlayCallbacks = false;
            HasPendingTakeOutCallbacks = false;
        }

        return result;
    }

    public void Abandon()
    {
        TryPreventKtapConnect();
        if (Interlocked.Exchange(ref _disposed, 1) == 0)
        {
            Record("Abandon");
            AbandonAction?.Invoke();
            IsConnected = false;
        }
    }

    public void Dispose()
    {
        if (Interlocked.Exchange(ref _disposed, 1) == 0)
        {
            Record("Dispose");
            IsConnected = false;
        }
    }

    private void Record(string name)
    {
        Calls.Enqueue(name);
        ThreadIds.Enqueue(Environment.CurrentManagedThreadId);
        ApartmentStates.Enqueue(Thread.CurrentThread.GetApartmentState());
        CallObserver?.Invoke(name);
    }
}

internal sealed class FakeRegistrationProbe : IK3dRegistrationProbe
{
    private int _probeCount;

    public FakeRegistrationProbe(K3dRegistrationReport? report = null)
    {
        Report = report ?? ReadyReport;
    }

    public static K3dRegistrationReport ReadyReport { get; } = new(
        K3dRegistrationIssue.None,
        Is64BitProcess: true,
        HasWin64TypeLibrary: true,
        IsWin64BinaryAmd64: true,
        IsEngineClassReady: true,
        IsEventHandlerClassReady: true,
        Message: "fake K3D registration ready");

    public K3dRegistrationReport Report { get; set; }

    public int ProbeCount => Volatile.Read(ref _probeCount);

    public K3dRegistrationReport Probe()
    {
        Interlocked.Increment(ref _probeCount);
        return Report;
    }
}

internal sealed class FakeTornadoProcessProbe : ITornadoProcessProbe
{
    private readonly object _sync = new();
    private readonly Queue<TornadoProcessSnapshot> _snapshots = new();
    private TornadoProcessSnapshot _last;

    public FakeTornadoProcessProbe(params TornadoProcessSnapshot[] snapshots)
    {
        _last = snapshots.LastOrDefault()
            ?? new TornadoProcessSnapshot(0, 0, 0);
        foreach (var snapshot in snapshots)
        {
            _snapshots.Enqueue(snapshot);
        }
    }

    public int CaptureCount { get; private set; }

    public Regex? LastPattern { get; private set; }

    public TornadoProcessSnapshot Capture(Regex? testWindowPattern)
    {
        lock (_sync)
        {
            CaptureCount++;
            LastPattern = testWindowPattern;
            if (_snapshots.Count > 0)
            {
                _last = _snapshots.Dequeue();
            }

            return _last;
        }
    }
}

internal sealed class FakeLiveAuthorization : ILiveAuthorization
{
    public FakeLiveAuthorization(bool isAuthorizedForThisLaunch)
    {
        IsAuthorizedForThisLaunch = isAuthorizedForThisLaunch;
    }

    public bool IsAuthorizedForThisLaunch { get; set; }
}

internal sealed class TrackingStaDispatcherFactory : IStaDispatcherFactory
{
    private int _createCount;

    public int CreateCount => Volatile.Read(ref _createCount);

    public ConcurrentQueue<IStaDispatcher> Dispatchers { get; } = new();

    public IStaDispatcher Create(int capacity)
    {
        Interlocked.Increment(ref _createCount);
        var dispatcher = new StaDispatcher(capacity);
        Dispatchers.Enqueue(dispatcher);
        return dispatcher;
    }
}
