#nullable enable

namespace MBN_STOCK_WEBVIEW.DbWriteSmoke;

internal enum DevelopmentDatabaseSmokeStage
{
    None,
    Preflight,
    Create,
    CreateReadback,
    Save,
    SaveReadback,
    Cleanup,
    VerifyAbsent
}

internal sealed record DevelopmentDatabaseSmokeResult(
    string ScenarioName,
    bool Passed,
    DevelopmentDatabaseSmokeStage FailureStage,
    string FailureClass,
    bool OutcomeUnknown,
    bool CleanupAttempted,
    bool CleanupVerified,
    bool ManualCleanupMayBeRequired);

/// <summary>
/// Runs one collision-isolated development write scenario. Every delegate is
/// invoked at most once. A mutation with an unknown outcome quarantines the
/// scenario immediately: no cleanup mutation or later scenario may be issued.
/// </summary>
internal sealed class DevelopmentDatabaseSmokeScenario
{
    private readonly Func<CancellationToken, Task> _preflight;
    private readonly Func<CancellationToken, Task> _create;
    private readonly Func<CancellationToken, Task> _verifyCreate;
    private readonly Func<CancellationToken, Task>? _save;
    private readonly Func<CancellationToken, Task>? _verifySave;
    private readonly Func<CancellationToken, Task> _cleanup;
    private readonly Func<CancellationToken, Task> _verifyAbsent;
    private readonly Func<Exception, bool> _isOutcomeUnknown;

    internal DevelopmentDatabaseSmokeScenario(
        string name,
        Func<CancellationToken, Task> preflight,
        Func<CancellationToken, Task> create,
        Func<CancellationToken, Task> verifyCreate,
        Func<CancellationToken, Task>? save,
        Func<CancellationToken, Task>? verifySave,
        Func<CancellationToken, Task> cleanup,
        Func<CancellationToken, Task> verifyAbsent,
        Func<Exception, bool> isOutcomeUnknown)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(name);
        if ((save is null) != (verifySave is null))
        {
            throw new ArgumentException(
                "Save and save-readback delegates must either both be present or both be absent.",
                nameof(save));
        }

        Name = name;
        _preflight = preflight ?? throw new ArgumentNullException(nameof(preflight));
        _create = create ?? throw new ArgumentNullException(nameof(create));
        _verifyCreate = verifyCreate ?? throw new ArgumentNullException(nameof(verifyCreate));
        _save = save;
        _verifySave = verifySave;
        _cleanup = cleanup ?? throw new ArgumentNullException(nameof(cleanup));
        _verifyAbsent = verifyAbsent ?? throw new ArgumentNullException(nameof(verifyAbsent));
        _isOutcomeUnknown = isOutcomeUnknown ??
            throw new ArgumentNullException(nameof(isOutcomeUnknown));
    }

    internal string Name { get; }

    internal async Task<DevelopmentDatabaseSmokeResult> RunAsync(
        CancellationToken cancellationToken = default)
    {
        var currentStage = DevelopmentDatabaseSmokeStage.Preflight;
        Exception? primaryFailure = null;
        var failureStage = DevelopmentDatabaseSmokeStage.None;
        var outcomeUnknown = false;
        var failureClass = "none";
        var createKnownCommitted = false;
        var cleanupAttempted = false;
        var cleanupVerified = false;

        try
        {
            try
            {
                await _preflight(cancellationToken).ConfigureAwait(false);

                currentStage = DevelopmentDatabaseSmokeStage.Create;
                await _create(cancellationToken).ConfigureAwait(false);
                createKnownCommitted = true;

                currentStage = DevelopmentDatabaseSmokeStage.CreateReadback;
                await _verifyCreate(cancellationToken).ConfigureAwait(false);

                if (_save is not null)
                {
                    currentStage = DevelopmentDatabaseSmokeStage.Save;
                    await _save(cancellationToken).ConfigureAwait(false);

                    currentStage = DevelopmentDatabaseSmokeStage.SaveReadback;
                    await _verifySave!(cancellationToken).ConfigureAwait(false);
                }
            }
            catch (Exception exception)
            {
                primaryFailure = exception;
                failureStage = currentStage;
                failureClass = RedactedDatabaseFailureClassifier.Classify(exception);
                outcomeUnknown = _isOutcomeUnknown(exception);
            }
        }
        finally
        {
            // A known successful create is cleaned up even when a later read-only
            // verification fails. An unknown mutation outcome is quarantined and
            // never followed by another mutation.
            if (createKnownCommitted && !outcomeUnknown)
            {
                cleanupAttempted = true;
                try
                {
                    currentStage = DevelopmentDatabaseSmokeStage.Cleanup;
                    await _cleanup(CancellationToken.None).ConfigureAwait(false);

                    currentStage = DevelopmentDatabaseSmokeStage.VerifyAbsent;
                    await _verifyAbsent(CancellationToken.None).ConfigureAwait(false);
                    cleanupVerified = true;
                }
                catch (Exception exception)
                {
                    primaryFailure = exception;
                    failureStage = currentStage;
                    failureClass = RedactedDatabaseFailureClassifier.Classify(exception);
                    outcomeUnknown = _isOutcomeUnknown(exception);
                }
            }
        }

        if (primaryFailure is null && cleanupVerified)
        {
            return new DevelopmentDatabaseSmokeResult(
                Name,
                Passed: true,
                DevelopmentDatabaseSmokeStage.None,
                "none",
                OutcomeUnknown: false,
                CleanupAttempted: true,
                CleanupVerified: true,
                ManualCleanupMayBeRequired: false);
        }

        return new DevelopmentDatabaseSmokeResult(
            Name,
            Passed: false,
            failureStage,
            failureClass,
            outcomeUnknown,
            cleanupAttempted,
            cleanupVerified,
            ManualCleanupMayBeRequired: outcomeUnknown &&
                (createKnownCommitted || failureStage == DevelopmentDatabaseSmokeStage.Create));
    }
}
