using MBN_STOCK_WEBVIEW.PlayoutSmoke;

namespace MBN_STOCK_WEBVIEW.Playout.Tests;

public sealed class SmokeCommandLineTests
{
    [Theory]
    [InlineData("Tornado2 TEST", false)]
    [InlineData("TEST-1", false)]
    [InlineData("Tornado2", true)]
    [InlineData("PGM", true)]
    [InlineData("CONTEST", true)]
    [InlineData("", true)]
    [InlineData(null, true)]
    public void TornadoWindowTitleWithoutExplicitTestMarkerIsUnsafe(
        string? title,
        bool expected)
    {
        Assert.Equal(expected, SmokeCommandLine.IsUnsafeTornadoWindowTitle(title));
    }

    [Fact]
    public void Parse_NoArguments_PreservesReadOnlyProbeDefault()
    {
        var parsed = SmokeCommandLine.Parse([]);

        Assert.True(parsed.IsValid);
        Assert.Equal(SmokeCommandKind.Probe, parsed.Invocation.Kind);
    }

    [Theory]
    [InlineData("--probe", "Probe")]
    [InlineData("--PROBE", "Probe")]
    [InlineData("--dry-run", "DryRun")]
    public void Parse_ExistingComFreeCommandsRemainAvailable(
        string argument,
        string expected)
    {
        var parsed = SmokeCommandLine.Parse([argument]);

        Assert.True(parsed.IsValid);
        Assert.Equal(expected, parsed.Invocation.Kind.ToString());
    }

    [Fact]
    public void Parse_TestConnectRequiresExplicitAcknowledgementAndConfigArgument()
    {
        var withoutAcknowledgement = SmokeCommandLine.Parse(
            ["--test-connect", "--config", "C:\\safe\\playout.local.json"]);
        var complete = SmokeCommandLine.Parse(
        [
            "--test-connect",
            SmokeCommandLine.AcknowledgementFlag,
            "--config",
            "C:\\safe\\playout.local.json"
        ]);

        Assert.False(withoutAcknowledgement.IsValid);
        Assert.Equal("missing-acknowledgement", withoutAcknowledgement.ErrorCode);
        Assert.True(complete.IsValid);
        Assert.Equal(SmokeCommandKind.TestConnect, complete.Invocation.Kind);
    }

    [Fact]
    public void Parse_TestSequenceRequiresBothSceneCodesAndBoundedObservation()
    {
        var missingObservation = SmokeCommandLine.Parse(
        [
            "--test-sequence",
            SmokeCommandLine.AcknowledgementFlag,
            "--config",
            "C:\\safe\\playout.local.json",
            "--prepare",
            "5001",
            "--next",
            "5006"
        ]);
        var complete = SmokeCommandLine.Parse(
        [
            "--test-sequence",
            "--prepare",
            "5001",
            "--observe-ms",
            "1000",
            SmokeCommandLine.AcknowledgementFlag,
            "--next",
            "5006",
            "--config",
            "C:\\safe\\playout.local.json"
        ]);

        Assert.False(missingObservation.IsValid);
        Assert.Equal("missing-required-option", missingObservation.ErrorCode);
        Assert.True(complete.IsValid);
        Assert.Equal(1000, complete.Invocation.ObservationMilliseconds);
    }

    [Theory]
    [InlineData("999")]
    [InlineData("30001")]
    [InlineData("1.5")]
    [InlineData("secret")]
    public void Parse_RejectsUnboundedOrNonIntegralObservation(string value)
    {
        var parsed = ValidSequenceArguments(observation: value);

        Assert.False(parsed.IsValid);
        Assert.Equal("invalid-observation-duration", parsed.ErrorCode);
    }

    [Theory]
    [InlineData("../5001")]
    [InlineData("5001.t2s")]
    [InlineData("장면")]
    [InlineData("SCENE A")]
    public void Parse_RejectsUnsafeSceneCodesWithoutReflectingThem(string sceneCode)
    {
        var parsed = ValidSequenceArguments(prepare: sceneCode);

        Assert.False(parsed.IsValid);
        Assert.Equal("unsafe-scene-code", parsed.ErrorCode);
        Assert.DoesNotContain(sceneCode, parsed.ErrorCode, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Parse_TestConnectRejectsMutationOptions()
    {
        var parsed = SmokeCommandLine.Parse(
        [
            "--test-connect",
            SmokeCommandLine.AcknowledgementFlag,
            "--config",
            "C:\\safe\\playout.local.json",
            "--prepare",
            "5001"
        ]);

        Assert.False(parsed.IsValid);
        Assert.Equal("unknown-option", parsed.ErrorCode);
    }

    [Fact]
    public void Parse_TestSequenceRequiresTwoDistinctSceneCodes()
    {
        var parsed = SmokeCommandLine.Parse(
        [
            "--test-sequence",
            SmokeCommandLine.AcknowledgementFlag,
            "--config",
            "C:\\safe\\playout.local.json",
            "--prepare",
            "5001",
            "--next",
            "5001",
            "--observe-ms",
            "1000"
        ]);

        Assert.False(parsed.IsValid);
        Assert.Equal("duplicate-scene-code", parsed.ErrorCode);
    }

    [Fact]
    public void Parse_TestPlanUsesTheSameSequenceInputsButNeverImpliesExecution()
    {
        var parsed = SmokeCommandLine.Parse(
        [
            "--test-plan",
            SmokeCommandLine.AcknowledgementFlag,
            "--config",
            "C:\\safe\\playout.local.json",
            "--prepare",
            "5001",
            "--next",
            "5006",
            "--observe-ms",
            "5000"
        ]);

        Assert.True(parsed.IsValid);
        Assert.Equal(SmokeCommandKind.TestPlan, parsed.Invocation.Kind);
    }

    [Fact]
    public void Parse_PgmConnectDiagnosticRequiresDistinctAcknowledgementAndExactInputs()
    {
        string[] complete =
        [
            "--pgm-connect-diagnostic",
            SmokeCommandLine.PgmConnectAcknowledgementFlag,
            "--host",
            "127.0.0.1",
            "--port",
            "30001",
            "--expected-pgm-window-title",
            "PGM"
        ];
        var missingAcknowledgement = SmokeCommandLine.Parse(complete[0..1]
            .Concat(complete[2..])
            .ToArray());
        var wrongAcknowledgement = SmokeCommandLine.Parse(
            complete.Select(value => value == SmokeCommandLine.PgmConnectAcknowledgementFlag
                ? SmokeCommandLine.AcknowledgementFlag
                : value).ToArray());
        var parsed = SmokeCommandLine.Parse(complete);

        Assert.False(missingAcknowledgement.IsValid);
        Assert.Equal("missing-acknowledgement", missingAcknowledgement.ErrorCode);
        Assert.False(wrongAcknowledgement.IsValid);
        Assert.Equal("unknown-option", wrongAcknowledgement.ErrorCode);
        Assert.True(parsed.IsValid);
        Assert.Equal(SmokeCommandKind.PgmConnectDiagnostic, parsed.Invocation.Kind);
        Assert.Equal("127.0.0.1", parsed.Invocation.Host);
        Assert.Equal(30001, parsed.Invocation.Port);
        Assert.Equal("PGM", parsed.Invocation.ExpectedPgmWindowTitle);
        Assert.Null(parsed.Invocation.ConfigPath);
    }

    [Theory]
    [InlineData("--config")]
    [InlineData("--prepare")]
    [InlineData("--next")]
    [InlineData("--observe-ms")]
    public void Parse_PgmConnectDiagnosticRejectsEveryConfigOrRenderOption(string option)
    {
        var parsed = SmokeCommandLine.Parse(
        [
            "--pgm-connect-diagnostic",
            SmokeCommandLine.PgmConnectAcknowledgementFlag,
            "--host",
            "127.0.0.1",
            "--port",
            "30001",
            "--expected-pgm-window-title",
            "PGM",
            option,
            "5001"
        ]);

        Assert.False(parsed.IsValid);
        Assert.Equal("unknown-option", parsed.ErrorCode);
    }

    [Theory]
    [InlineData("0")]
    [InlineData("65536")]
    [InlineData("secret")]
    public void Parse_PgmConnectDiagnosticRejectsInvalidPort(string port)
    {
        var parsed = SmokeCommandLine.Parse(
        [
            "--pgm-connect-diagnostic",
            SmokeCommandLine.PgmConnectAcknowledgementFlag,
            "--host",
            "127.0.0.1",
            "--port",
            port,
            "--expected-pgm-window-title",
            "PGM"
        ]);

        Assert.False(parsed.IsValid);
        Assert.Equal("invalid-port", parsed.ErrorCode);
    }

    [Fact]
    public void Parse_PgmCutsSequenceRequiresItsOwnLongAcknowledgementAndFixedInputs()
    {
        var complete = ValidPgmCutsArguments();
        var missingAcknowledgement = SmokeCommandLine.Parse(
            complete.Where(value => value != SmokeCommandLine.PgmCutsSequenceAcknowledgementFlag)
                .ToArray());
        var wrongAcknowledgement = SmokeCommandLine.Parse(
            complete.Select(value => value == SmokeCommandLine.PgmCutsSequenceAcknowledgementFlag
                ? SmokeCommandLine.AcknowledgementFlag
                : value).ToArray());
        var parsed = SmokeCommandLine.Parse(complete);
        var explicitObservation = SmokeCommandLine.Parse(
            complete.Concat(["--observe-ms", "5000"]).ToArray());

        Assert.False(missingAcknowledgement.IsValid);
        Assert.Equal("missing-acknowledgement", missingAcknowledgement.ErrorCode);
        Assert.False(wrongAcknowledgement.IsValid);
        Assert.Equal("unknown-option", wrongAcknowledgement.ErrorCode);
        Assert.True(parsed.IsValid);
        Assert.True(explicitObservation.IsValid);
        Assert.Equal(SmokeCommandKind.PgmCutsSequence, parsed.Invocation.Kind);
        Assert.Equal(5_000, parsed.Invocation.ObservationMilliseconds);
        Assert.Equal("C:\\Cuts", parsed.Invocation.SceneRoot);
        Assert.Null(parsed.Invocation.ConfigPath);
        Assert.Null(parsed.Invocation.PrepareSceneCode);
        Assert.Null(parsed.Invocation.NextSceneCode);
    }

    [Theory]
    [InlineData("--config")]
    [InlineData("--prepare")]
    [InlineData("--next")]
    public void Parse_PgmCutsSequenceRejectsConfigAndCallerSelectedScenes(string option)
    {
        var parsed = SmokeCommandLine.Parse(
            ValidPgmCutsArguments().Concat([option, "sensitive-value"]).ToArray());

        Assert.False(parsed.IsValid);
        Assert.Equal("unknown-option", parsed.ErrorCode);
    }

    [Theory]
    [InlineData("4999")]
    [InlineData("5001")]
    [InlineData("secret")]
    public void Parse_PgmCutsSequenceOnlyAcceptsExplicitFiveSecondObservation(string value)
    {
        var parsed = SmokeCommandLine.Parse(
            ValidPgmCutsArguments().Concat(["--observe-ms", value]).ToArray());

        Assert.False(parsed.IsValid);
        Assert.Equal("invalid-observation-duration", parsed.ErrorCode);
    }

    [Theory]
    [InlineData("localhost", "C:\\Cuts")]
    [InlineData("192.0.2.1", "C:\\Cuts")]
    [InlineData("127.0.0.1", "relative-cuts")]
    public void Parse_PgmCutsSequenceRequiresLiteralLoopbackAndAbsoluteSceneRoot(
        string host,
        string sceneRoot)
    {
        var arguments = ValidPgmCutsArguments();
        arguments[Array.IndexOf(arguments, "127.0.0.1")] = host;
        arguments[Array.IndexOf(arguments, "C:\\Cuts")] = sceneRoot;

        var parsed = SmokeCommandLine.Parse(arguments);

        Assert.False(parsed.IsValid);
        Assert.Equal("missing-or-invalid-required-option", parsed.ErrorCode);
    }

    private static SmokeCommandParseResult ValidSequenceArguments(
        string prepare = "5001",
        string observation = "1000") =>
        SmokeCommandLine.Parse(
        [
            "--test-sequence",
            SmokeCommandLine.AcknowledgementFlag,
            "--config",
            "C:\\safe\\playout.local.json",
            "--prepare",
            prepare,
            "--next",
            "5006",
            "--observe-ms",
            observation
        ]);

    private static string[] ValidPgmCutsArguments() =>
    [
        "--pgm-cuts-sequence",
        SmokeCommandLine.PgmCutsSequenceAcknowledgementFlag,
        "--host",
        "127.0.0.1",
        "--port",
        "30001",
        "--expected-pgm-window-title",
        "Tornado2 PGM",
        "--scene-root",
        "C:\\Cuts"
    ];
}
