using System.Diagnostics;
using System.Text.Json;
using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Playout;
using MBN_STOCK_WEBVIEW.Playout.Configuration;
using MBN_STOCK_WEBVIEW.Playout.Diagnostics;
using MBN_STOCK_WEBVIEW.Playout.Registration;
using MBN_STOCK_WEBVIEW.PlayoutSmoke;

var parsed = SmokeCommandLine.Parse(args);
if (!parsed.IsValid)
{
    return Usage(parsed.ErrorCode);
}

using var cancellation = new CancellationTokenSource();
Console.CancelKeyPress += (_, eventArgs) =>
{
    eventArgs.Cancel = true;
    cancellation.Cancel();
};

return parsed.Invocation.Kind switch
{
    SmokeCommandKind.Probe => Probe(),
    SmokeCommandKind.DryRun => await DryRunAsync(),
    SmokeCommandKind.TestPlan => TestPlan(parsed.Invocation),
    SmokeCommandKind.TestConnect or SmokeCommandKind.TestSequence =>
        await RunIsolatedTestCommandAsync(parsed.Invocation, cancellation.Token),
    SmokeCommandKind.PgmConnectDiagnostic =>
        await RunPgmConnectDiagnosticAsync(parsed.Invocation, cancellation.Token),
    SmokeCommandKind.PgmCutsSequence =>
        await RunPgmCutsSequenceAsync(parsed.Invocation, cancellation.Token),
    _ => Usage("unknown-command")
};

static int Probe()
{
    var runtimeRegistration = new K3dRegistrationProbe().Probe();
    var processInspectionAvailable = true;
    var tornadoProcessCount = 0;
    var programWindowDetected = false;
    Process[] processes;
    try
    {
        processes = Process.GetProcesses();
    }
    catch
    {
        processes = [];
        processInspectionAvailable = false;
    }

    foreach (var process in processes)
    {
        try
        {
            if (!process.ProcessName.StartsWith("Tornado2", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            tornadoProcessCount++;
            var title = process.MainWindowTitle;
            if (SmokeCommandLine.IsUnsafeTornadoWindowTitle(title))
            {
                programWindowDetected = true;
            }
        }
        catch
        {
            // A process may exit or deny inspection between enumeration and reading.
        }
        finally
        {
            process.Dispose();
        }
    }

    WriteJson(new
    {
        command = "probe",
        architecture = Environment.Is64BitProcess ? "x64" : "x86",
        connectRequestIssued = false,
        comActivationAttempted = false,
        lastKtapConnectState = "not-attempted",
        ktapConnectAttempted = false,
        ktapConnectAccepted = false,
        ktapHelloObserved = (bool?)null,
        networkMonitoringRecordExpected = false,
        networkMonitoringCheckRequired = false,
        networkMonitoringVerified = (bool?)null,
        runtimeRegistration = new
        {
            ready = runtimeRegistration.IsReady,
            issues = runtimeRegistration.Issues.ToString(),
            engineClassReady = runtimeRegistration.IsEngineClassReady,
            eventHandlerClassReady = runtimeRegistration.IsEventHandlerClassReady,
            message = runtimeRegistration.Message
        },
        tornado = new
        {
            inspectionAvailable = processInspectionAvailable,
            processCount = tornadoProcessCount,
            programWindowDetected
        },
        safety = programWindowDetected
            ? "PROGRAM or ambiguous Tornado window detected; no COM activation was attempted."
            : "Read-only probe complete; no COM activation was attempted."
    });

    return runtimeRegistration.IsReady && Environment.Is64BitProcess
        ? 0
        : 2;
}

static async Task<int> DryRunAsync()
{
    var options = new PlayoutOptions { Mode = PlayoutMode.DryRun };
    await using var engine = PlayoutEngineFactory.Create(options);
    var cue = new PlayoutCue("DRY_RUN_ONLY.t2s", "DRY_RUN_ONLY");

    var results = new[]
    {
        await engine.ConnectAsync(),
        await engine.PrepareAsync(cue),
        await engine.TakeInAsync(),
        await engine.NextAsync(cue with { SceneName = "DRY_RUN_NEXT" }),
        await engine.TakeOutAsync(PlayoutTakeOutScope.All),
        await engine.DisconnectAsync()
    };

    WriteJson(new
    {
        command = "dry-run",
        mode = engine.Status.Mode.ToString(),
        state = engine.Status.State.ToString(),
        connectRequestIssued = true,
        comActivationAttempted = false,
        lastKtapConnectState = "not-attempted",
        ktapConnectAttempted = false,
        ktapConnectAccepted = false,
        ktapHelloObserved = (bool?)null,
        networkMonitoringRecordExpected = false,
        networkMonitoringCheckRequired = false,
        networkMonitoringVerified = (bool?)null,
        commands = results.Select(result => new
        {
            operation = result.Operation.ToString(),
            code = result.Code.ToString(),
            message = result.Message
        })
    });

    return results.All(result => result.IsSuccess) ? 0 : 3;
}

static int TestPlan(SmokeCommandInvocation invocation)
{
    try
    {
        var plan = IsolatedTestCommandPreflight.Create(invocation);
        WriteJson(new
        {
            command = "test-plan",
            mode = "Test",
            preflightValidated = true,
            runtimeProcessGateChecked = false,
            connectRequestIssued = false,
            comActivationAttempted = false,
            lastKtapConnectState = "not-attempted",
            ktapConnectAttempted = false,
            ktapConnectAccepted = false,
            ktapHelloObserved = (bool?)null,
            networkMonitoringRecordExpected = false,
            networkMonitoringCheckRequired = false,
            networkMonitoringVerified = (bool?)null,
            sceneCount = plan.PrepareCue is null ? 0 : 2,
            automaticReconnectEnabled = plan.Options.ReconnectEnabled,
            safety = "Configuration and scene assets validated only; no engine or COM was created."
        });
        return 0;
    }
    catch (IsolatedTestPreflightException exception)
    {
        WritePreflightFailure("test-plan", exception.ErrorCode);
        return 65;
    }
    catch
    {
        WritePreflightFailure("test-plan", "internal-preflight-failure");
        return 70;
    }
}

static async Task<int> RunIsolatedTestCommandAsync(
    SmokeCommandInvocation invocation,
    CancellationToken cancellationToken)
{
    var command = invocation.Kind == SmokeCommandKind.TestConnect
        ? "test-connect"
        : "test-sequence";
    IsolatedTestCommandPlan plan;
    try
    {
        plan = IsolatedTestCommandPreflight.Create(invocation);
    }
    catch (IsolatedTestPreflightException exception)
    {
        WritePreflightFailure(command, exception.ErrorCode);
        return 65;
    }
    catch
    {
        WritePreflightFailure(command, "internal-preflight-failure");
        return 70;
    }

    var report = await IsolatedTestCommandExecutor.ExecuteAsync(plan, cancellationToken);
    WriteJson(report);
    if (report.Completed && !report.OutcomeUnknown)
    {
        return 0;
    }

    return report.OutcomeUnknown ? 5 : 4;
}

static async Task<int> RunPgmConnectDiagnosticAsync(
    SmokeCommandInvocation invocation,
    CancellationToken cancellationToken)
{
    if (IsolatedTestCommandPreflight.HasPlayoutEnvironmentOverride(
            Environment.GetEnvironmentVariables()))
    {
        WritePgmDiagnosticFailure("environment-override-present");
        return 65;
    }

    var request = new PgmConnectDiagnosticRequest(
        invocation.Host ?? string.Empty,
        invocation.Port ?? 0,
        invocation.ExpectedPgmWindowTitle ?? string.Empty);
    PgmConnectDiagnosticReport report;
    try
    {
        report = await new PgmConnectDiagnosticRunner()
            .ExecuteAsync(request, cancellationToken)
            .ConfigureAwait(false);
    }
    catch
    {
        WritePgmDiagnosticFailure("internal-diagnostic-failure");
        return 70;
    }

    WriteJson(new
    {
        command = "pgm-connect-diagnostic",
        report.RequestValidated,
        report.ProcessGatePassed,
        report.ListenerOwnershipConfirmed,
        report.RegistrationReady,
        report.ConnectRequestIssued,
        report.ComActivationAttempted,
        lastKtapConnectState = ToWireValue(report.LastKtapConnectState),
        report.KtapConnectAttempted,
        report.KtapConnectAccepted,
        report.KtapHelloObserved,
        report.NetworkMonitoringRecordExpected,
        report.NetworkMonitoringCheckRequired,
        report.NetworkMonitoringVerified,
        report.DisconnectAttempted,
        report.DisconnectCode,
        report.Completed,
        report.OutcomeUnknown,
        report.RenderCommandSurfaceExposed,
        report.RenderCommandAttempted,
        report.AutomaticReconnectEnabled,
        report.ErrorCode
    });

    if (report.Completed && !report.OutcomeUnknown)
    {
        return 0;
    }

    return report.OutcomeUnknown ? 5 : 4;
}

static async Task<int> RunPgmCutsSequenceAsync(
    SmokeCommandInvocation invocation,
    CancellationToken cancellationToken)
{
    if (IsolatedTestCommandPreflight.HasPlayoutEnvironmentOverride(
            Environment.GetEnvironmentVariables()))
    {
        WritePgmCutsSequenceFailure("environment-override-present");
        return 65;
    }

    var request = new PgmCutsSequenceRequest(
        invocation.Host ?? string.Empty,
        invocation.Port ?? 0,
        invocation.ExpectedPgmWindowTitle ?? string.Empty,
        invocation.SceneRoot ?? string.Empty,
        invocation.ObservationMilliseconds ?? 0);
    PgmCutsSequenceReport report;
    try
    {
        report = await new PgmCutsSequenceRunner()
            .ExecuteAsync(
                request,
                stage =>
                {
                    WriteProgressJson(new
                    {
                        @event = "observation-start",
                        stage = ToObservationWireValue(stage)
                    });
                },
                cancellationToken)
            .ConfigureAwait(false);
    }
    catch
    {
        WritePgmCutsSequenceFailure("internal-sequence-failure");
        return 70;
    }

    WriteJson(new
    {
        command = "pgm-cuts-sequence",
        report.RequestValidated,
        report.HashPinsPresent,
        report.TargetGatePassed,
        report.EngineCreated,
        report.RenderCommandAttempted,
        report.DisconnectAttempted,
        report.QuarantineAttempted,
        report.QuarantineCompleted,
        report.Completed,
        report.OutcomeUnknown,
        commands = report.Commands.Select(command => new
        {
            stage = ToSequenceStageWireValue(command.Stage),
            code = command.Code.ToString()
        }),
        report.ErrorCode
    });

    if (report.Completed && !report.OutcomeUnknown)
    {
        return 0;
    }

    return report.OutcomeUnknown ? 5 : 4;
}

static void WritePgmDiagnosticFailure(string errorCode) =>
    WriteJson(new
    {
        command = "pgm-connect-diagnostic",
        requestValidated = false,
        processGatePassed = false,
        listenerOwnershipConfirmed = false,
        registrationReady = false,
        connectRequestIssued = false,
        comActivationAttempted = false,
        lastKtapConnectState = "not-attempted",
        ktapConnectAttempted = false,
        ktapConnectAccepted = false,
        ktapHelloObserved = (bool?)null,
        networkMonitoringRecordExpected = false,
        networkMonitoringCheckRequired = false,
        networkMonitoringVerified = (bool?)null,
        disconnectAttempted = false,
        disconnectCode = "not-attempted",
        completed = false,
        outcomeUnknown = false,
        renderCommandSurfaceExposed = false,
        renderCommandAttempted = false,
        automaticReconnectEnabled = false,
        errorCode
    });

static void WritePgmCutsSequenceFailure(string errorCode) =>
    WriteJson(new
    {
        command = "pgm-cuts-sequence",
        requestValidated = false,
        hashPinsPresent = false,
        targetGatePassed = false,
        engineCreated = false,
        renderCommandAttempted = false,
        disconnectAttempted = false,
        quarantineAttempted = false,
        quarantineCompleted = false,
        completed = false,
        outcomeUnknown = false,
        commands = Array.Empty<object>(),
        errorCode
    });

static string ToWireValue(PlayoutKtapConnectState state) => state switch
{
    PlayoutKtapConnectState.NotAttempted => "not-attempted",
    PlayoutKtapConnectState.Attempted => "attempted",
    PlayoutKtapConnectState.AcceptedUnconfirmed => "accepted-unconfirmed",
    PlayoutKtapConnectState.Failed => "failed",
    _ => "not-attempted"
};

static string ToObservationWireValue(PgmCutsObservationStage stage) => stage switch
{
    PgmCutsObservationStage.First => "first",
    PgmCutsObservationStage.Next => "next",
    PgmCutsObservationStage.TakeOut => "take-out",
    _ => "unknown"
};

static string ToSequenceStageWireValue(PgmCutsSequenceStage stage) => stage switch
{
    PgmCutsSequenceStage.Connect => "connect",
    PgmCutsSequenceStage.PrepareFirst => "prepare-first",
    PgmCutsSequenceStage.TakeIn => "take-in",
    PgmCutsSequenceStage.Next => "next",
    PgmCutsSequenceStage.TakeOut => "take-out",
    PgmCutsSequenceStage.Disconnect => "disconnect",
    _ => "unknown"
};

static void WritePreflightFailure(string command, string errorCode) =>
    WriteJson(new
    {
        command,
        mode = "Test",
        preflightValidated = false,
        runtimeProcessGateChecked = false,
        connectRequestIssued = false,
        comActivationAttempted = false,
        lastKtapConnectState = "not-attempted",
        ktapConnectAttempted = false,
        ktapConnectAccepted = false,
        ktapHelloObserved = (bool?)null,
        networkMonitoringRecordExpected = false,
        networkMonitoringCheckRequired = false,
        networkMonitoringVerified = (bool?)null,
        errorCode,
        safety = "Rejected before engine creation; no COM activation was attempted."
    });

static void WriteJson<T>(T value) =>
    Console.WriteLine(JsonSerializer.Serialize(value, new JsonSerializerOptions
    {
        WriteIndented = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    }));

static void WriteProgressJson<T>(T value)
{
    Console.WriteLine(JsonSerializer.Serialize(value, new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    }));
    Console.Out.Flush();
}

static int Usage(string? errorCode)
{
    WriteJson(new
    {
        command = "invalid",
        connectRequestIssued = false,
        comActivationAttempted = false,
        lastKtapConnectState = "not-attempted",
        ktapConnectAttempted = false,
        ktapConnectAccepted = false,
        ktapHelloObserved = (bool?)null,
        networkMonitoringRecordExpected = false,
        networkMonitoringCheckRequired = false,
        networkMonitoringVerified = (bool?)null,
        errorCode = errorCode ?? "invalid-command-line"
    });
    Console.Error.WriteLine(
        "Usage: MBN_STOCK_WEBVIEW.PlayoutSmoke [--probe|--dry-run|--test-plan|--test-connect|--test-sequence|--pgm-connect-diagnostic|--pgm-cuts-sequence]");
    Console.Error.WriteLine(
        $"Test commands require {SmokeCommandLine.AcknowledgementFlag} and --config <absolute-path>.");
    Console.Error.WriteLine(
        "--test-plan and --test-sequence also require --prepare <scene-code> --next <scene-code> --observe-ms <1000..30000>.");
    Console.Error.WriteLine(
        "--test-connect and --test-sequence may request render-capable COM after all Test safety gates pass.");
    Console.Error.WriteLine(
        $"--pgm-connect-diagnostic requires {SmokeCommandLine.PgmConnectAcknowledgementFlag}, --host, --port and --expected-pgm-window-title; it exposes no render command.");
    Console.Error.WriteLine(
        $"--pgm-cuts-sequence requires {SmokeCommandLine.PgmCutsSequenceAcknowledgementFlag}, --host, --port, --expected-pgm-window-title and --scene-root; --observe-ms may only be 5000.");
    return 64;
}
