using System.CommandLine;
using ZB.MOM.WW.ScadaBridge.CLI.Commands;

namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;

/// <summary>
/// #54: <c>template script add/update</c> must expose <c>--min-time-between-runs</c>
/// (a duration with ms/s/min units) and <c>--execution-timeout-seconds</c> (an int)
/// so the per-script throttle/re-fire interval and the execution-timeout override —
/// previously settable only via Transport bundle import — are CLI-authorable. These
/// tests pin the option surface and the duration-parsing semantics of
/// <see cref="TemplateCommands.TryParseMinTimeBetweenRuns"/>.
/// </summary>
public class TemplateScriptTimingTests
{
    private static readonly Option<string> Url = new("--url") { Recursive = true };
    private static readonly Option<string> Username = new("--username") { Recursive = true };
    private static readonly Option<string> Password = new("--password") { Recursive = true };
    private static readonly Option<string> Format = CliOptions.CreateFormatOption();

    private static Command ScriptGroup()
        => TemplateCommands.Build(Url, Format, Username, Password)
            .Subcommands.Single(c => c.Name == "script");

    // ---- option surface ----

    [Fact]
    public void ScriptAdd_HasTimingOptions()
    {
        var add = ScriptGroup().Subcommands.Single(c => c.Name == "add");
        var names = add.Options.Select(o => o.Name).ToArray();
        Assert.Contains("--min-time-between-runs", names);
        Assert.Contains("--execution-timeout-seconds", names);
    }

    [Fact]
    public void ScriptUpdate_HasTimingOptions()
    {
        var update = ScriptGroup().Subcommands.Single(c => c.Name == "update");
        var names = update.Options.Select(o => o.Name).ToArray();
        Assert.Contains("--min-time-between-runs", names);
        Assert.Contains("--execution-timeout-seconds", names);
    }

    // ---- --min-time-between-runs parsing ----

    [Fact]
    public void MinTime_Absent_IsUnsetNoError()
    {
        Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(null, out var d, out var err));
        Assert.Null(d);
        Assert.Null(err);
    }

    [Fact]
    public void MinTime_Blank_IsUnsetNoError()
    {
        Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns("   ", out var d, out var err));
        Assert.Null(d);
        Assert.Null(err);
    }

    [Theory]
    [InlineData("500ms", 500)]
    [InlineData("250MS", 250)]
    public void MinTime_Milliseconds_Parsed(string value, int expectedMs)
    {
        Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
        Assert.Null(err);
        Assert.Equal(TimeSpan.FromMilliseconds(expectedMs), d);
    }

    [Theory]
    [InlineData("5")]      // bare number → seconds
    [InlineData("5s")]
    [InlineData("5sec")]
    [InlineData("5SEC")]
    public void MinTime_Seconds_BareDefaultsToSeconds(string value)
    {
        Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
        Assert.Null(err);
        Assert.Equal(TimeSpan.FromSeconds(5), d);
    }

    [Theory]
    [InlineData("2m")]
    [InlineData("2min")]
    [InlineData("2MIN")]
    public void MinTime_Minutes_Parsed(string value)
    {
        Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
        Assert.Null(err);
        Assert.Equal(TimeSpan.FromMinutes(2), d);
    }

    [Theory]
    [InlineData("0")]
    [InlineData("0s")]
    [InlineData("0min")]
    public void MinTime_Zero_IsUnset(string value)
    {
        // 0 means "no throttle" → null, mirroring DurationInput.Compose's non-positive handling.
        Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
        Assert.Null(err);
        Assert.Null(d);
    }

    [Theory]
    [InlineData("abc")]
    [InlineData("5h")]      // hours not supported
    [InlineData("5days")]
    [InlineData("-3")]
    [InlineData("1.5s")]    // non-integer
    public void MinTime_Invalid_ReturnsError(string value)
    {
        Assert.False(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
        Assert.Null(d);
        Assert.NotNull(err);
    }

    /// <summary>
    /// An absurdly large value (16-digit millisecond count) would overflow long/TimeSpan
    /// without the overflow guard.  The parser must return false cleanly — not throw.
    /// </summary>
    [Theory]
    [InlineData("9999999999999999ms")]   // 16-digit ms: far exceeds TimeSpan.MaxValue (~922337203685477ms)
    [InlineData("9999999999999999s")]    // 16-digit seconds: even larger when scaled
    [InlineData("9999999999999999min")]  // 16-digit minutes
    public void MinTime_AbsurdlyLarge_ReturnsFalseWithoutThrowing(string value)
    {
        // Must not throw — TryParse contract: never throws, always returns bool.
        var threw = false;
        bool result = false;
        TimeSpan? duration = null;
        string? error = null;
        try
        {
            result = TemplateCommands.TryParseMinTimeBetweenRuns(value, out duration, out error);
        }
        catch
        {
            threw = true;
        }

        Assert.False(threw, "TryParseMinTimeBetweenRuns must not throw on large input");
        Assert.False(result);
        Assert.Null(duration);
        Assert.NotNull(error);
    }
}
