using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
using MBN_STOCK_WEBVIEW.Playout.Configuration;
using MBN_STOCK_WEBVIEW.Playout.Diagnostics;
using MBN_STOCK_WEBVIEW.Playout.Interop;
using MBN_STOCK_WEBVIEW.Playout.Registration;
using MBN_STOCK_WEBVIEW.Playout.Runtime;
using MBN_STOCK_WEBVIEW.Playout.Safety;
using System.Net;

namespace MBN_STOCK_WEBVIEW.Playout;

public static class PlayoutEngineFactory
{
    public static IPlayoutEngine CreateDefault() =>
        Create(
            PlayoutOptionsLoader.Load(),
            PlayoutLaunchAuthorization.CaptureFromEnvironment());

    public static IPlayoutEngine Create(PlayoutOptions options) =>
        Create(options, PlayoutLaunchAuthorization.CaptureFromEnvironment());

    public static IPlayoutEngine Create(
        PlayoutOptions options,
        PlayoutLaunchAuthorization launchAuthorization)
    {
        ArgumentNullException.ThrowIfNull(options);
        ArgumentNullException.ThrowIfNull(launchAuthorization);
        ValidateGateAOptions(options, launchAuthorization);
        var validated = ValidatedPlayoutOptions.Create(options);
        ITornadoProcessProbe processProbe = new TornadoProcessProbe();
        Func<bool>? finalConnectSafetyCheck = null;
        var abandonOnTargetMismatch = false;
        var startMonitor = validated.Mode != PlayoutMode.Disabled;

        if (launchAuthorization.IsGateA)
        {
            var safetyProbe = new WindowsPgmDiagnosticSafetyProbe();
            var request = new PgmConnectDiagnosticRequest(
                validated.Host,
                validated.Port,
                "PGM");
            var parsedHost = IPAddress.Parse(validated.Host);
            var baseline = safetyProbe.Capture(request, parsedHost);
            if (!baseline.IsEligible ||
                baseline.Identity is not { } identity ||
                identity.ProcessId != launchAuthorization.ExpectedPgmProcessId ||
                identity.StartTimeUtcTicks !=
                    launchAuthorization.ExpectedPgmStartTimeUtcTicks)
            {
                throw new PlayoutConfigurationException(
                    "Gate A could not confirm the exact approved PGM process and listener.");
            }

            bool HasExactTarget()
            {
                try
                {
                    var current = safetyProbe.Capture(request, parsedHost);
                    return current.IsEligible && baseline.HasSameTarget(current);
                }
                catch
                {
                    return false;
                }
            }

            processProbe = new PgmCutsSequenceProcessProbe(
                safetyProbe,
                request,
                parsedHost,
                baseline);
            finalConnectSafetyCheck = HasExactTarget;
            abandonOnTargetMismatch = true;
            startMonitor = false;
        }

        IStaDispatcher? dispatcher = validated.Mode is PlayoutMode.Test or PlayoutMode.Live
            ? new StaDispatcher(validated.QueueCapacity)
            : null;
        return new TornadoPlayoutEngine(
            validated,
            new DynamicK3dSessionFactory(
                rollbackOnPrepareFailure: !launchAuthorization.IsGateA),
            new K3dRegistrationProbe(),
            processProbe,
            dispatcher,
            launchAuthorization,
            TimeProvider.System,
            startMonitor,
            finalConnectSafetyCheck,
            abandonOnTargetMismatch,
            beforeSdkDispatchClaim: launchAuthorization.IsGateA
                ? launchAuthorization.EnsureGateASdkDispatchAuthorized
                : null,
            launchAuthorization: launchAuthorization);
    }

    internal static void ValidateGateAOptions(
        PlayoutOptions options,
        PlayoutLaunchAuthorization launchAuthorization)
    {
        if (!launchAuthorization.IsGateA)
        {
            return;
        }

        var allowlist = (options.TestSceneAllowlist ?? [])
            .Select(static value => value?.Trim())
            .ToArray();
        if (options.Mode != PlayoutMode.Live ||
            !options.TrustedLiveOutputEnabled ||
            !string.Equals(options.Host?.Trim(), "127.0.0.1", StringComparison.Ordinal) ||
            options.Port != 30001 ||
            options.TcpMode != 1 ||
            options.ClientPort != 0 ||
            options.OutputChannel is not null ||
            options.LayoutIndex != 10 ||
            !PlayoutLaunchAuthorization.IsExactGateASceneDirectory(
                options.SceneDirectory) ||
            allowlist.Length != 1 ||
            !string.Equals(allowlist[0], "5001", StringComparison.Ordinal) ||
            options.ReconnectEnabled ||
            options.MaximumReconnectAttempts != 0 ||
            options.MaximumAutomaticRefreshesPerTakeIn != 0 ||
            options.LegacySceneFadeDuration != 6 ||
            options.LegacySceneBackgroundKind != LegacySceneBackgroundKind.None ||
            !string.IsNullOrWhiteSpace(options.LegacySceneBackgroundAssetPath))
        {
            throw new PlayoutConfigurationException(
                "Gate A requires the exact closed 5001-only Live configuration.");
        }
    }

    /// <summary>
    /// Applies the same fail-closed Test-mode configuration, scene-root, file-name and
    /// allowlist validation used by the runtime before a diagnostic tool creates an engine.
    /// This method never creates a dispatcher or activates COM.
    /// </summary>
    public static void ValidateIsolatedTestCommand(
        PlayoutOptions options,
        IEnumerable<PlayoutCue> cues)
    {
        ArgumentNullException.ThrowIfNull(options);
        ArgumentNullException.ThrowIfNull(cues);

        if (options.Mode != PlayoutMode.Test || options.TrustedLiveOutputEnabled)
        {
            throw new PlayoutConfigurationException(
                "The isolated test command requires Test mode with live output disabled.");
        }

        var validated = ValidatedPlayoutOptions.Create(options);
        foreach (var cue in cues)
        {
            if (cue is null)
            {
                throw new PlayoutConfigurationException(
                    "The isolated test command contains an invalid scene.");
            }

            PlayoutCue resolved;
            try
            {
                resolved = validated.ResolveCue(cue);
            }
            catch (PlayoutRequestException exception)
            {
                throw new PlayoutConfigurationException(
                    "The isolated test command contains an invalid scene.",
                    exception);
            }

            if (!validated.IsTestSceneAllowed(resolved))
            {
                throw new PlayoutConfigurationException(
                    "The isolated test command contains a scene that is not allowlisted.");
            }
        }
    }
}
