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

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

/// <summary>
/// CLI-019 regression tests for <see cref="BundleCommands.StreamBase64ToFile"/>.
/// The pre-fix code did <c>Convert.FromBase64String(...) → File.WriteAllBytes(...)</c>,
/// doubling the bundle's bytes onto the LOH and writing synchronously. The new
/// streaming helper decodes the base64 string in fixed-size chunks straight into
/// a <see cref="FileStream"/>, so peak working set is bounded by the chunk size
/// regardless of how large the bundle is.
/// </summary>
public class BundleCommandsStreamingTests : IDisposable
{
    private readonly string _tempPath;

    public BundleCommandsStreamingTests()
    {
        _tempPath = Path.Combine(Path.GetTempPath(), $"bundle-stream-test-{Guid.NewGuid():N}.bin");
    }

    public void Dispose()
    {
        if (File.Exists(_tempPath))
        {
            File.Delete(_tempPath);
        }
    }

    [Fact]
    public void StreamBase64ToFile_SmallPayload_RoundTrips()
    {
        var bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        var base64 = Convert.ToBase64String(bytes);

        var written = BundleCommands.StreamBase64ToFile(base64, _tempPath);

        Assert.Equal(bytes.Length, written);
        var roundTripped = File.ReadAllBytes(_tempPath);
        Assert.Equal(bytes, roundTripped);
    }

    [Fact]
    public void StreamBase64ToFile_PayloadCrossesChunkBoundary_RoundTrips()
    {
        // Build a payload several chunks wide so the slicing loop runs more than
        // once, with enough trailing bytes that the final slice is short and
        // exercises the padding/short-final-chunk path.
        var size = (BundleCommands.Base64StreamChunkChars / 4 * 3) * 3 + 17;
        var bytes = new byte[size];
        for (var i = 0; i < size; i++) bytes[i] = (byte)(i & 0xFF);

        var base64 = Convert.ToBase64String(bytes);

        var written = BundleCommands.StreamBase64ToFile(base64, _tempPath);

        Assert.Equal(size, written);
        var roundTripped = File.ReadAllBytes(_tempPath);
        Assert.Equal(bytes, roundTripped);
    }

    [Fact]
    public void StreamBase64ToFile_EmptyString_WritesEmptyFile()
    {
        var written = BundleCommands.StreamBase64ToFile(string.Empty, _tempPath);

        Assert.Equal(0, written);
        Assert.True(File.Exists(_tempPath));
        Assert.Empty(File.ReadAllBytes(_tempPath));
    }

    [Fact]
    public void StreamBase64ToFile_InvalidBase64_ThrowsFormatException()
    {
        // '*' is not a valid base64 character, so TryFromBase64Chars returns
        // false and the helper throws — the pre-fix code threw FormatException
        // from Convert.FromBase64String, so the contract is preserved.
        var invalid = "this is not valid base64 !!!*";

        Assert.Throws<FormatException>(() => BundleCommands.StreamBase64ToFile(invalid, _tempPath));
    }

    [Fact]
    public void StreamBase64ToFile_NullBase64_Throws()
    {
        Assert.Throws<ArgumentNullException>(() => BundleCommands.StreamBase64ToFile(null!, _tempPath));
    }

    [Fact]
    public void StreamBase64ToFile_EmptyOutputPath_Throws()
    {
        Assert.Throws<ArgumentException>(() => BundleCommands.StreamBase64ToFile("AAAA", string.Empty));
    }
}
