#nullable enable

using MBN_STOCK_WEBVIEW.DbWriteSmoke;

namespace MBN_STOCK_WEBVIEW.DbWriteSmoke.Tests;

public sealed class DevelopmentDatabaseSmokeScenarioTests
{
    [Fact]
    public async Task SuccessfulScenario_InvokesEachOperationExactlyOnceInOrder()
    {
        var calls = new List<string>();
        var scenario = CreateScenario(calls);

        var result = await scenario.RunAsync();

        Assert.True(result.Passed);
        Assert.True(result.CleanupAttempted);
        Assert.True(result.CleanupVerified);
        Assert.Equal(
            ["preflight", "create", "create-readback", "save", "save-readback", "cleanup", "absent"],
            calls);
    }

    [Fact]
    public async Task UnknownCreate_DoesNotRetryCleanupOrContinue()
    {
        var calls = new List<string>();
        var scenario = CreateScenario(
            calls,
            create: _ =>
            {
                calls.Add("create");
                return Task.FromException(new UnknownTestException());
            });

        var result = await scenario.RunAsync();

        Assert.False(result.Passed);
        Assert.True(result.OutcomeUnknown);
        Assert.Equal(nameof(UnknownTestException), result.FailureClass);
        Assert.True(result.ManualCleanupMayBeRequired);
        Assert.False(result.CleanupAttempted);
        Assert.Equal(["preflight", "create"], calls);
    }

    [Fact]
    public async Task UnknownSave_DoesNotIssueReadbackOrCleanupMutation()
    {
        var calls = new List<string>();
        var scenario = CreateScenario(
            calls,
            save: _ =>
            {
                calls.Add("save");
                return Task.FromException(new UnknownTestException());
            });

        var result = await scenario.RunAsync();

        Assert.False(result.Passed);
        Assert.True(result.OutcomeUnknown);
        Assert.True(result.ManualCleanupMayBeRequired);
        Assert.False(result.CleanupAttempted);
        Assert.Equal(["preflight", "create", "create-readback", "save"], calls);
    }

    [Fact]
    public async Task KnownReadbackFailure_StillCleansKnownCreatedRecordInFinally()
    {
        var calls = new List<string>();
        var scenario = CreateScenario(
            calls,
            verifyCreate: _ =>
            {
                calls.Add("create-readback");
                return Task.FromException(new InvalidOperationException("bad readback"));
            });

        var result = await scenario.RunAsync();

        Assert.False(result.Passed);
        Assert.False(result.OutcomeUnknown);
        Assert.Equal(nameof(InvalidOperationException), result.FailureClass);
        Assert.Equal(DevelopmentDatabaseSmokeStage.CreateReadback, result.FailureStage);
        Assert.True(result.CleanupAttempted);
        Assert.True(result.CleanupVerified);
        Assert.Equal(
            ["preflight", "create", "create-readback", "cleanup", "absent"],
            calls);
    }

    [Fact]
    public async Task UnknownCleanup_IsNotRetriedAndSkipsAbsenceRead()
    {
        var calls = new List<string>();
        var scenario = CreateScenario(
            calls,
            cleanup: _ =>
            {
                calls.Add("cleanup");
                return Task.FromException(new UnknownTestException());
            });

        var result = await scenario.RunAsync();

        Assert.False(result.Passed);
        Assert.True(result.OutcomeUnknown);
        Assert.Equal(DevelopmentDatabaseSmokeStage.Cleanup, result.FailureStage);
        Assert.True(result.CleanupAttempted);
        Assert.False(result.CleanupVerified);
        Assert.Equal(
            ["preflight", "create", "create-readback", "save", "save-readback", "cleanup"],
            calls);
    }

    private static DevelopmentDatabaseSmokeScenario CreateScenario(
        List<string> calls,
        Func<CancellationToken, Task>? create = null,
        Func<CancellationToken, Task>? verifyCreate = null,
        Func<CancellationToken, Task>? save = null,
        Func<CancellationToken, Task>? cleanup = null) =>
        new(
            "fake production workflow",
            Record(calls, "preflight"),
            create ?? Record(calls, "create"),
            verifyCreate ?? Record(calls, "create-readback"),
            save ?? Record(calls, "save"),
            Record(calls, "save-readback"),
            cleanup ?? Record(calls, "cleanup"),
            Record(calls, "absent"),
            exception => exception is UnknownTestException);

    private static Func<CancellationToken, Task> Record(
        ICollection<string> calls,
        string value) =>
        _ =>
        {
            calls.Add(value);
            return Task.CompletedTask;
        };

    private sealed class UnknownTestException : Exception;
}
