using System.Net;
using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Playout.Registration;
using MBN_STOCK_WEBVIEW.Playout.Runtime;

namespace MBN_STOCK_WEBVIEW.Playout.Diagnostics;

internal sealed record PgmConnectDiagnosticRequest(
    string Host,
    int Port,
    string ExpectedPgmWindowTitle);

internal sealed record PgmConnectDiagnosticReport(
    bool RequestValidated,
    bool ProcessGatePassed,
    bool ListenerOwnershipConfirmed,
    bool RegistrationReady,
    bool ConnectRequestIssued,
    bool? ComActivationAttempted,
    PlayoutKtapConnectState LastKtapConnectState,
    bool KtapConnectAttempted,
    bool? KtapConnectAccepted,
    bool? KtapHelloObserved,
    bool? NetworkMonitoringRecordExpected,
    bool NetworkMonitoringCheckRequired,
    bool? NetworkMonitoringVerified,
    bool DisconnectAttempted,
    string DisconnectCode,
    bool Completed,
    bool OutcomeUnknown,
    bool RenderCommandSurfaceExposed,
    bool RenderCommandAttempted,
    bool AutomaticReconnectEnabled,
    string? ErrorCode);

internal sealed class PgmConnectDiagnosticRunner
{
    internal static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(3);
    internal static readonly TimeSpan DisconnectTimeout = TimeSpan.FromSeconds(3);

    private readonly IPgmDiagnosticSafetyProbe _safetyProbe;
    private readonly IKtapConnectOnlySessionFactory _sessionFactory;
    private readonly IK3dRegistrationProbe _registrationProbe;
    private readonly IStaDispatcherFactory _dispatcherFactory;
    private readonly TimeSpan _connectTimeout;
    private readonly TimeSpan _disconnectTimeout;

    internal PgmConnectDiagnosticRunner()
        : this(
            new WindowsPgmDiagnosticSafetyProbe(),
            new DynamicKtapConnectOnlySessionFactory(),
            new K3dRegistrationProbe(),
            new StaDispatcherFactory(),
            ConnectTimeout,
            DisconnectTimeout)
    {
    }

    internal PgmConnectDiagnosticRunner(
        IPgmDiagnosticSafetyProbe safetyProbe,
        IKtapConnectOnlySessionFactory sessionFactory,
        IK3dRegistrationProbe registrationProbe,
        IStaDispatcherFactory dispatcherFactory,
        TimeSpan? connectTimeout = null,
        TimeSpan? disconnectTimeout = null)
    {
        _safetyProbe = safetyProbe;
        _sessionFactory = sessionFactory;
        _registrationProbe = registrationProbe;
        _dispatcherFactory = dispatcherFactory;
        _connectTimeout = connectTimeout ?? ConnectTimeout;
        _disconnectTimeout = disconnectTimeout ?? DisconnectTimeout;
        if (_connectTimeout <= TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(connectTimeout));
        }

        if (_disconnectTimeout <= TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(disconnectTimeout));
        }
    }

    internal async Task<PgmConnectDiagnosticReport> ExecuteAsync(
        PgmConnectDiagnosticRequest request,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(request);
        if (!TryValidateRequest(request, out var parsedHost))
        {
            return Empty("request-rejected");
        }

        var validatedHost = parsedHost!;

        PgmDiagnosticTargetSnapshot baseline;
        try
        {
            baseline = _safetyProbe.Capture(request, validatedHost);
        }
        catch
        {
            return Empty("process-listener-inspection-unavailable") with
            {
                RequestValidated = true
            };
        }

        if (!baseline.IsEligible)
        {
            return Empty("pgm-target-rejected") with
            {
                RequestValidated = true,
                ProcessGatePassed = baseline.ProcessGatePassed,
                ListenerOwnershipConfirmed = baseline.ListenerOwnershipConfirmed
            };
        }

        K3dRegistrationReport registration;
        try
        {
            registration = _registrationProbe.Probe();
        }
        catch
        {
            return Empty("registration-inspection-unavailable") with
            {
                RequestValidated = true,
                ProcessGatePassed = true,
                ListenerOwnershipConfirmed = true
            };
        }

        if (!registration.IsReady)
        {
            return Empty("registration-not-ready") with
            {
                RequestValidated = true,
                ProcessGatePassed = true,
                ListenerOwnershipConfirmed = true
            };
        }

        var state = new ExecutionState
        {
            RequestValidated = true,
            ProcessGatePassed = true,
            ListenerOwnershipConfirmed = true,
            RegistrationReady = true,
            ConnectRequestIssued = true
        };
        IKtapConnectOnlySession? pendingSession = null;
        IKtapConnectOnlySession? acceptedSession = null;
        await using var dispatcher = _dispatcherFactory.Create(1);

        try
        {
            var session = _sessionFactory.Create();
            Volatile.Write(ref pendingSession, session);
            acceptedSession = await dispatcher.InvokeAsync(
                () =>
                {
                    var current = _safetyProbe.Capture(request, validatedHost);
                    if (!current.IsEligible || !baseline.HasSameTarget(current))
                    {
                        throw new PgmDiagnosticSafetyException();
                    }

                    session.Connect(
                        validatedHost.ToString(),
                        request.Port,
                        () =>
                        {
                            var final = _safetyProbe.Capture(request, validatedHost);
                            return final.IsEligible && baseline.HasSameTarget(final);
                        });
                    return session;
                },
                _connectTimeout,
                cancellationToken,
                static lateSession => lateSession.Abandon(),
                () => Volatile.Read(ref pendingSession)?.TryPreventKtapConnect())
                .ConfigureAwait(false);

            CaptureSessionEvidence(state, acceptedSession);
            state.DisconnectAttempted = true;
            await dispatcher.InvokeAsync(
                () =>
                {
                    var current = _safetyProbe.Capture(request, validatedHost);
                    if (!current.IsEligible || !baseline.HasSameTarget(current))
                    {
                        acceptedSession.Abandon();
                        throw new PgmDiagnosticSafetyException();
                    }

                    acceptedSession.Disconnect();
                    return true;
                },
                _disconnectTimeout,
                CancellationToken.None)
                .ConfigureAwait(false);

            state.DisconnectCode = "success";
            state.Completed = true;
        }
        catch (PgmDiagnosticSafetyException)
        {
            CaptureSessionEvidence(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = state.KtapConnectAttempted
                ? "pgm-target-changed-after-connect"
                : "pgm-target-changed-before-connect";
            state.OutcomeUnknown = state.KtapConnectAttempted;
            state.DisconnectCode = state.DisconnectAttempted
                ? "not-issued-target-changed"
                : "not-attempted";
        }
        catch (KtapConnectRejectedException)
        {
            CaptureSessionEvidence(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = "ktap-connect-rejected";
        }
        catch (KtapConnectInvocationException exception)
        {
            CaptureSessionEvidence(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = exception.SafeErrorCode;
            state.OutcomeUnknown = true;
        }
        catch (OperationCanceledException)
        {
            PreventLateDispatchAndCapture(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = "cancelled";
            state.OutcomeUnknown = state.KtapConnectAttempted;
        }
        catch (StaOperationTimedOutException)
        {
            PreventLateDispatchAndCapture(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = state.DisconnectAttempted
                ? "disconnect-timeout"
                : "connect-timeout";
            state.DisconnectCode = state.DisconnectAttempted
                ? "outcome-unknown"
                : "not-attempted";
            state.OutcomeUnknown = state.KtapConnectAttempted || state.DisconnectAttempted;
        }
        catch (StaDispatcherQuarantinedException)
        {
            PreventLateDispatchAndCapture(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = "dispatcher-quarantined";
            state.OutcomeUnknown = state.KtapConnectAttempted || state.DisconnectAttempted;
        }
        catch (InstalledK3dInteropMetadataUnavailableException)
        {
            CaptureSessionEvidence(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = "installed-interop-metadata-unavailable";
            state.DisconnectCode = "not-attempted";
        }
        catch
        {
            CaptureSessionEvidence(state, Volatile.Read(ref pendingSession));
            state.ErrorCode = state.DisconnectAttempted
                ? "disconnect-failed"
                : state.KtapConnectAttempted
                    ? "ktap-connect-failed"
                    : "com-activation-failed";
            state.DisconnectCode = state.DisconnectAttempted ? "failed" : "not-attempted";
            state.OutcomeUnknown = state.KtapConnectAttempted || state.DisconnectAttempted;
        }
        finally
        {
            if (!state.Completed && acceptedSession is not null && !dispatcher.IsQuarantined)
            {
                try
                {
                    await dispatcher.InvokeAsync(
                        () =>
                        {
                            acceptedSession.Abandon();
                            return true;
                        },
                        _disconnectTimeout,
                        CancellationToken.None)
                        .ConfigureAwait(false);
                }
                catch
                {
                    // Local COM release is best effort. Never issue a second SDK command here.
                }
            }
        }

        return state.ToReport();
    }

    internal static bool TryValidateRequest(
        PgmConnectDiagnosticRequest request,
        out IPAddress? host)
    {
        host = null;
        if (request.Port is < 1 or > 65_535 ||
            string.IsNullOrWhiteSpace(request.Host) ||
            !IPAddress.TryParse(request.Host, out host) ||
            !IPAddress.IsLoopback(host) ||
            string.IsNullOrWhiteSpace(request.ExpectedPgmWindowTitle) ||
            request.ExpectedPgmWindowTitle.Length > 128 ||
            HasToken(request.ExpectedPgmWindowTitle, "TEST") ||
            !(HasToken(request.ExpectedPgmWindowTitle, "PGM") ||
              HasToken(request.ExpectedPgmWindowTitle, "PROGRAM")))
        {
            host = null;
            return false;
        }

        return true;
    }

    private static bool HasToken(string value, string token)
    {
        for (var start = 0; start <= value.Length - token.Length; start++)
        {
            if (!value.AsSpan(start, token.Length).Equals(
                    token.AsSpan(),
                    StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var beforeIsBoundary = start == 0 || !char.IsLetterOrDigit(value[start - 1]);
            var after = start + token.Length;
            var afterIsBoundary = after == value.Length || !char.IsLetterOrDigit(value[after]);
            if (beforeIsBoundary && afterIsBoundary)
            {
                return true;
            }
        }

        return false;
    }

    private static void PreventLateDispatchAndCapture(
        ExecutionState state,
        IKtapConnectOnlySession? session)
    {
        session?.TryPreventKtapConnect();
        CaptureSessionEvidence(state, session);
    }

    private static void CaptureSessionEvidence(
        ExecutionState state,
        IKtapConnectOnlySession? session)
    {
        if (session is null)
        {
            return;
        }

        state.ComActivationAttempted = session.ComActivationAttempted;
        state.LastKtapConnectState = session.LastKtapConnectState;
        state.KtapConnectAttempted =
            session.LastKtapConnectState != PlayoutKtapConnectState.NotAttempted;
    }

    private static PgmConnectDiagnosticReport Empty(string errorCode) => new(
        false,
        false,
        false,
        false,
        false,
        false,
        PlayoutKtapConnectState.NotAttempted,
        false,
        false,
        null,
        false,
        false,
        null,
        false,
        "not-attempted",
        false,
        false,
        false,
        false,
        false,
        errorCode);

    private sealed class ExecutionState
    {
        public bool RequestValidated { get; set; }
        public bool ProcessGatePassed { get; set; }
        public bool ListenerOwnershipConfirmed { get; set; }
        public bool RegistrationReady { get; set; }
        public bool ConnectRequestIssued { get; set; }
        public bool? ComActivationAttempted { get; set; } = false;
        public PlayoutKtapConnectState LastKtapConnectState { get; set; }
        public bool KtapConnectAttempted { get; set; }
        public bool DisconnectAttempted { get; set; }
        public string DisconnectCode { get; set; } = "not-attempted";
        public bool Completed { get; set; }
        public bool OutcomeUnknown { get; set; }
        public string? ErrorCode { get; set; }

        public PgmConnectDiagnosticReport ToReport()
        {
            bool? accepted = LastKtapConnectState switch
            {
                PlayoutKtapConnectState.AcceptedUnconfirmed => true,
                PlayoutKtapConnectState.NotAttempted => false,
                _ => null
            };
            bool? monitoringExpected = LastKtapConnectState switch
            {
                PlayoutKtapConnectState.AcceptedUnconfirmed => true,
                PlayoutKtapConnectState.NotAttempted => false,
                _ => null
            };

            return new PgmConnectDiagnosticReport(
                RequestValidated,
                ProcessGatePassed,
                ListenerOwnershipConfirmed,
                RegistrationReady,
                ConnectRequestIssued,
                ComActivationAttempted,
                LastKtapConnectState,
                KtapConnectAttempted,
                accepted,
                null,
                monitoringExpected,
                KtapConnectAttempted,
                null,
                DisconnectAttempted,
                DisconnectCode,
                Completed,
                OutcomeUnknown,
                false,
                false,
                false,
                ErrorCode);
        }
    }
}

internal sealed class PgmDiagnosticSafetyException : Exception;

internal sealed class KtapConnectRejectedException : Exception;

internal sealed class KtapConnectInvocationException : Exception
{
    public KtapConnectInvocationException(string safeErrorCode, Exception innerException)
        : base("The KTAP invocation failed.", innerException)
    {
        SafeErrorCode = safeErrorCode;
    }

    public string SafeErrorCode { get; }
}
