using System.Globalization;
using System.Runtime.ExceptionServices;
using MBN_STOCK_WEBVIEW.Core.Playout;

namespace MBN_STOCK_WEBVIEW.Playout.Diagnostics;

internal interface IKtapConnectOnlySession
{
    bool ComActivationAttempted { get; }

    PlayoutKtapConnectState LastKtapConnectState { get; }

    bool TryPreventKtapConnect();

    void Connect(string host, int port, Func<bool> finalSafetyCheck);

    void Disconnect();

    void Abandon();
}

internal interface IKtapConnectOnlySessionFactory
{
    IKtapConnectOnlySession Create();
}

internal sealed class DynamicKtapConnectOnlySessionFactory : IKtapConnectOnlySessionFactory
{
    private readonly IK3dConnectOnlyComBindingFactory _bindingFactory;

    public DynamicKtapConnectOnlySessionFactory()
        : this(new InstalledK3dInteropConnectBindingFactory())
    {
    }

    internal DynamicKtapConnectOnlySessionFactory(IK3dConnectOnlyComBindingFactory bindingFactory)
    {
        _bindingFactory = bindingFactory;
    }

    public IKtapConnectOnlySession Create() =>
        new DynamicKtapConnectOnlySession(_bindingFactory.Create());
}

internal sealed class DynamicKtapConnectOnlySession : IKtapConnectOnlySession
{
    private readonly IK3dConnectOnlyComBinding _binding;
    private object? _engine;
    private object? _eventHandler;
    private int? _ownerThreadId;
    private int _lastKtapConnectState;
    private int _ktapDispatchGate;
    private bool _accepted;
    private bool _abandoned;

    public DynamicKtapConnectOnlySession(IK3dConnectOnlyComBinding binding)
    {
        _binding = binding;
    }

    public bool ComActivationAttempted { get; private set; }

    public PlayoutKtapConnectState LastKtapConnectState =>
        (PlayoutKtapConnectState)Volatile.Read(ref _lastKtapConnectState);

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

        return previous == 0;
    }

    public void Connect(string host, int port, Func<bool> finalSafetyCheck)
    {
        ArgumentNullException.ThrowIfNull(finalSafetyCheck);
        EnsureThread();
        if (_abandoned || _engine is not null || _accepted)
        {
            throw new InvalidOperationException("The connection-only session is unavailable.");
        }

        if (Volatile.Read(ref _ktapDispatchGate) != 0)
        {
            throw new InvalidOperationException(
                "The connection-only diagnostic was stopped before COM activation.");
        }

        var ktapDispatchAttempted = false;
        try
        {
            ComActivationAttempted = true;
            _eventHandler = _binding.CreateEventHandler();
            _engine = _binding.CreateEngine();
            if (Volatile.Read(ref _ktapDispatchGate) != 0)
            {
                throw new InvalidOperationException(
                    "The connection-only diagnostic was stopped during COM activation.");
            }

            if (!finalSafetyCheck())
            {
                throw new PgmDiagnosticSafetyException();
            }

            if (Interlocked.CompareExchange(ref _ktapDispatchGate, 1, 0) != 0)
            {
                throw new InvalidOperationException(
                    "The connection-only diagnostic was stopped before KTAP dispatch.");
            }

            SetKtapConnectState(PlayoutKtapConnectState.Attempted);
            ktapDispatchAttempted = true;
            var returnValue = _binding.KtapConnect(
                _engine,
                host,
                port,
                _eventHandler);
            if (returnValue != 1)
            {
                SetKtapConnectState(PlayoutKtapConnectState.Failed);
                throw new KtapConnectRejectedException();
            }

            _accepted = true;
            SetKtapConnectState(PlayoutKtapConnectState.AcceptedUnconfirmed);
        }
        catch (Exception exception)
        {
            if (LastKtapConnectState == PlayoutKtapConnectState.Attempted)
            {
                SetKtapConnectState(PlayoutKtapConnectState.Failed);
            }

            _accepted = false;
            ReleaseAll();
            if (exception is KtapConnectRejectedException || !ktapDispatchAttempted)
            {
                throw;
            }

            throw new KtapConnectInvocationException(
                ClassifyInvocationFailure(exception),
                exception);
        }
    }

    public void Disconnect()
    {
        EnsureThread();
        if (_abandoned || !_accepted || _engine is null)
        {
            throw new InvalidOperationException("No accepted KTAP session is available.");
        }

        Exception? failure = null;
        try
        {
            _binding.Disconnect(_engine);
        }
        catch (Exception exception)
        {
            failure = exception;
        }
        finally
        {
            _accepted = false;
            ReleaseAll();
        }

        if (failure is not null)
        {
            ExceptionDispatchInfo.Capture(failure).Throw();
        }
    }

    public void Abandon()
    {
        if (_abandoned)
        {
            return;
        }

        EnsureThread();
        TryPreventKtapConnect();
        _abandoned = true;
        _accepted = false;
        ReleaseAll();
    }

    private void SetKtapConnectState(PlayoutKtapConnectState state) =>
        Volatile.Write(ref _lastKtapConnectState, (int)state);

    private void EnsureThread()
    {
        var currentThreadId = Environment.CurrentManagedThreadId;
        if (_ownerThreadId is null)
        {
            if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
            {
                throw new InvalidOperationException("K3D access requires an STA thread.");
            }

            _ownerThreadId = currentThreadId;
            return;
        }

        if (_ownerThreadId.Value != currentThreadId)
        {
            throw new InvalidOperationException("K3D access changed STA threads.");
        }
    }

    private void ReleaseAll()
    {
        var engine = _engine;
        _engine = null;
        var eventHandler = _eventHandler;
        _eventHandler = null;
        Release(engine);
        Release(eventHandler);
    }

    private void Release(object? value)
    {
        if (value is null)
        {
            return;
        }

        try
        {
            _binding.Release(value);
        }
        catch
        {
            // Local RCW release is best effort and must not cause another SDK command.
        }
    }

    private static string ClassifyInvocationFailure(Exception exception) => exception switch
    {
        InvalidCastException => "ktap-argument-cast-failed",
        ArgumentException => "ktap-argument-binding-failed",
        _ => "ktap-connect-invocation-failed"
    };
}
