namespace MBN_STOCK_WEBVIEW.PlayoutSmoke;

internal enum SmokeCommandKind
{
    Invalid,
    Probe,
    DryRun,
    TestPlan,
    TestConnect,
    TestSequence,
    PgmConnectDiagnostic,
    PgmCutsSequence
}

internal sealed record SmokeCommandInvocation(
    SmokeCommandKind Kind,
    string? ConfigPath = null,
    string? PrepareSceneCode = null,
    string? NextSceneCode = null,
    int? ObservationMilliseconds = null,
    string? Host = null,
    int? Port = null,
    string? ExpectedPgmWindowTitle = null,
    string? SceneRoot = null);

internal sealed record SmokeCommandParseResult(
    bool IsValid,
    SmokeCommandInvocation Invocation,
    string? ErrorCode = null);

internal static class SmokeCommandLine
{
    internal const string AcknowledgementFlag =
        "--acknowledge-isolated-non-program-test-output";
    internal const string PgmConnectAcknowledgementFlag =
        "--i-understand-this-will-contact-current-pgm-tornado-via-ktap-connect-and-disconnect-only";
    internal const string PgmCutsSequenceAcknowledgementFlag =
        "--i-understand-this-will-render-current-pgm-tornado-cuts-5001-then-5006-and-take-them-out";

    public static SmokeCommandParseResult Parse(IReadOnlyList<string> arguments)
    {
        ArgumentNullException.ThrowIfNull(arguments);
        if (arguments.Count == 0)
        {
            return Valid(new SmokeCommandInvocation(SmokeCommandKind.Probe));
        }

        if (arguments.Count == 1)
        {
            return arguments[0].ToLowerInvariant() switch
            {
                "--probe" => Valid(new SmokeCommandInvocation(SmokeCommandKind.Probe)),
                "--dry-run" => Valid(new SmokeCommandInvocation(SmokeCommandKind.DryRun)),
                _ => Invalid("unknown-command")
            };
        }

        var kind = arguments[0].ToLowerInvariant() switch
        {
            "--test-plan" => SmokeCommandKind.TestPlan,
            "--test-connect" => SmokeCommandKind.TestConnect,
            "--test-sequence" => SmokeCommandKind.TestSequence,
            "--pgm-connect-diagnostic" => SmokeCommandKind.PgmConnectDiagnostic,
            "--pgm-cuts-sequence" => SmokeCommandKind.PgmCutsSequence,
            _ => SmokeCommandKind.Invalid
        };
        if (kind == SmokeCommandKind.Invalid)
        {
            return Invalid("unknown-command");
        }

        string? configPath = null;
        string? prepareSceneCode = null;
        string? nextSceneCode = null;
        int? observationMilliseconds = null;
        string? host = null;
        int? port = null;
        string? expectedPgmWindowTitle = null;
        string? sceneRoot = null;
        var acknowledged = false;

        for (var index = 1; index < arguments.Count; index++)
        {
            var argument = arguments[index];
            switch (argument)
            {
                case AcknowledgementFlag when kind is not (
                    SmokeCommandKind.PgmConnectDiagnostic or SmokeCommandKind.PgmCutsSequence):
                case PgmConnectAcknowledgementFlag when kind == SmokeCommandKind.PgmConnectDiagnostic:
                case PgmCutsSequenceAcknowledgementFlag when kind == SmokeCommandKind.PgmCutsSequence:
                    if (acknowledged)
                    {
                        return Invalid("duplicate-option");
                    }

                    acknowledged = true;
                    break;

                case "--host" when kind is
                    SmokeCommandKind.PgmConnectDiagnostic or SmokeCommandKind.PgmCutsSequence:
                    if (host is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out host))
                    {
                        return Invalid("missing-option-value");
                    }

                    break;

                case "--port" when kind is
                    SmokeCommandKind.PgmConnectDiagnostic or SmokeCommandKind.PgmCutsSequence:
                    if (port is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out var portText) ||
                        !int.TryParse(
                            portText,
                            System.Globalization.NumberStyles.None,
                            System.Globalization.CultureInfo.InvariantCulture,
                            out var parsedPort) ||
                        parsedPort is < 1 or > 65_535)
                    {
                        return Invalid("invalid-port");
                    }

                    port = parsedPort;
                    break;

                case "--expected-pgm-window-title"
                    when kind is
                        SmokeCommandKind.PgmConnectDiagnostic or SmokeCommandKind.PgmCutsSequence:
                    if (expectedPgmWindowTitle is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out expectedPgmWindowTitle))
                    {
                        return Invalid("missing-option-value");
                    }

                    break;

                case "--scene-root" when kind == SmokeCommandKind.PgmCutsSequence:
                    if (sceneRoot is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out sceneRoot))
                    {
                        return Invalid("missing-option-value");
                    }

                    break;

                case "--config" when kind is not (
                    SmokeCommandKind.PgmConnectDiagnostic or SmokeCommandKind.PgmCutsSequence):
                    if (configPath is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out configPath))
                    {
                        return Invalid("missing-option-value");
                    }

                    break;

                case "--prepare" when kind is SmokeCommandKind.TestPlan or SmokeCommandKind.TestSequence:
                    if (prepareSceneCode is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out prepareSceneCode))
                    {
                        return Invalid("missing-option-value");
                    }

                    break;

                case "--next" when kind is SmokeCommandKind.TestPlan or SmokeCommandKind.TestSequence:
                    if (nextSceneCode is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out nextSceneCode))
                    {
                        return Invalid("missing-option-value");
                    }

                    break;

                case "--observe-ms" when kind is SmokeCommandKind.TestPlan or SmokeCommandKind.TestSequence:
                    if (observationMilliseconds is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out var observationText) ||
                        !int.TryParse(
                            observationText,
                            System.Globalization.NumberStyles.None,
                            System.Globalization.CultureInfo.InvariantCulture,
                            out var observationValue) ||
                        observationValue is < 1_000 or > 30_000)
                    {
                        return Invalid("invalid-observation-duration");
                    }

                    observationMilliseconds = observationValue;
                    break;

                case "--observe-ms" when kind == SmokeCommandKind.PgmCutsSequence:
                    if (observationMilliseconds is not null)
                    {
                        return Invalid("duplicate-option");
                    }

                    if (!TryReadValue(arguments, ref index, out var pgmObservationText) ||
                        !int.TryParse(
                            pgmObservationText,
                            System.Globalization.NumberStyles.None,
                            System.Globalization.CultureInfo.InvariantCulture,
                            out var pgmObservationValue) ||
                        pgmObservationValue != 5_000)
                    {
                        return Invalid("invalid-observation-duration");
                    }

                    observationMilliseconds = pgmObservationValue;
                    break;

                default:
                    return Invalid("unknown-option");
            }
        }

        if (!acknowledged)
        {
            return Invalid("missing-acknowledgement");
        }

        if (kind == SmokeCommandKind.PgmConnectDiagnostic)
        {
            if (string.IsNullOrWhiteSpace(host) ||
                port is null ||
                string.IsNullOrWhiteSpace(expectedPgmWindowTitle))
            {
                return Invalid("missing-required-option");
            }

            return Valid(new SmokeCommandInvocation(
                kind,
                Host: host,
                Port: port,
                ExpectedPgmWindowTitle: expectedPgmWindowTitle));
        }

        if (kind == SmokeCommandKind.PgmCutsSequence)
        {
            if (string.IsNullOrWhiteSpace(host) ||
                !System.Net.IPAddress.TryParse(host, out var literalHost) ||
                !System.Net.IPAddress.IsLoopback(literalHost) ||
                port is null ||
                string.IsNullOrWhiteSpace(expectedPgmWindowTitle) ||
                string.IsNullOrWhiteSpace(sceneRoot) ||
                !Path.IsPathFullyQualified(sceneRoot))
            {
                return Invalid("missing-or-invalid-required-option");
            }

            return Valid(new SmokeCommandInvocation(
                kind,
                ObservationMilliseconds: observationMilliseconds ?? 5_000,
                Host: host,
                Port: port,
                ExpectedPgmWindowTitle: expectedPgmWindowTitle,
                SceneRoot: sceneRoot));
        }

        if (string.IsNullOrWhiteSpace(configPath))
        {
            return Invalid("missing-required-option");
        }

        if (kind is SmokeCommandKind.TestPlan or SmokeCommandKind.TestSequence)
        {
            if (prepareSceneCode is null || nextSceneCode is null || observationMilliseconds is null)
            {
                return Invalid("missing-required-option");
            }

            if (!IsSafeSceneCode(prepareSceneCode) || !IsSafeSceneCode(nextSceneCode))
            {
                return Invalid("unsafe-scene-code");
            }

            if (string.Equals(
                    prepareSceneCode,
                    nextSceneCode,
                    StringComparison.OrdinalIgnoreCase))
            {
                return Invalid("duplicate-scene-code");
            }
        }

        return Valid(new SmokeCommandInvocation(
            kind,
            configPath,
            prepareSceneCode,
            nextSceneCode,
            observationMilliseconds,
            host,
            port,
            expectedPgmWindowTitle,
            sceneRoot));
    }

    internal static bool IsSafeSceneCode(string value)
    {
        if (value.Length is < 1 or > 64)
        {
            return false;
        }

        foreach (var character in value)
        {
            if (!((character >= 'A' && character <= 'Z') ||
                  (character >= 'a' && character <= 'z') ||
                  (character >= '0' && character <= '9') ||
                  character is '_' or '-'))
            {
                return false;
            }
        }

        return true;
    }

    internal static bool IsUnsafeTornadoWindowTitle(string? title)
    {
        if (string.IsNullOrWhiteSpace(title) ||
            title.Contains("PGM", StringComparison.OrdinalIgnoreCase) ||
            title.Contains("PROGRAM", StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        const string marker = "TEST";
        for (var start = 0; start <= title.Length - marker.Length; start++)
        {
            if (!title.AsSpan(start, marker.Length).Equals(
                    marker.AsSpan(),
                    StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

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

        return true;
    }

    private static bool TryReadValue(
        IReadOnlyList<string> arguments,
        ref int index,
        out string? value)
    {
        if (index + 1 >= arguments.Count ||
            string.IsNullOrWhiteSpace(arguments[index + 1]) ||
            arguments[index + 1].StartsWith("--", StringComparison.Ordinal))
        {
            value = null;
            return false;
        }

        value = arguments[++index];
        return true;
    }

    private static SmokeCommandParseResult Valid(SmokeCommandInvocation invocation) =>
        new(true, invocation);

    private static SmokeCommandParseResult Invalid(string errorCode) =>
        new(false, new SmokeCommandInvocation(SmokeCommandKind.Invalid), errorCode);
}
