using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening;

namespace ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests;

/// <summary>
/// The staleness fast path: <see cref="StaleInstanceProbe"/> memoises a computed
/// revision hash against the <see cref="ITemplateGraphWatermark"/> readings it was
/// computed under, and the memo is PROCESS-STATIC. That makes the watermark the
/// only thing standing between a cached hash and a wrong answer — so these tests
/// pin that the memo is served only while the watermark has not moved, and is
/// dropped the moment it has.
///
/// <para>
/// The failure this guards against is a real one: a data-connection edit, or a
/// bundle import committing through the raw <c>DbContext</c>, changes the flattened
/// output without going through the repository path that derives bumps from the
/// change tracker. If that write does not bump, the memo below keeps answering with
/// the old hash forever.
/// </para>
/// </summary>
public class StaleInstanceProbeTests
{
    private const int InstanceId = 1;

    private readonly TemplateGraphWatermark _watermark = new();
    private readonly IFlatteningPipeline _pipeline = Substitute.For<IFlatteningPipeline>();
    private string _currentHash = "sha256:first";
    private int _flattenCount;

    public StaleInstanceProbeTests()
    {
        // Process-static memo: start every test from a clean slate.
        StaleInstanceProbe.ClearMemos();

        _pipeline.CreateSession().Returns(_ => new FlattenSession(_watermark));
        _pipeline
            .FlattenAndValidateAsync(InstanceId, Arg.Any<CancellationToken>(), Arg.Any<bool>(), Arg.Any<FlattenSession?>())
            .Returns(_ =>
            {
                _flattenCount++;
                var config = new FlattenedConfiguration { InstanceUniqueName = "Inst-01" };
                return Result<FlatteningPipelineResult>.Success(
                    new FlatteningPipelineResult(config, _currentHash, ValidationResult.Success()));
            });
    }

    private StaleInstanceProbe CreateProbe() => new(_pipeline, _watermark);

    [Fact]
    public async Task UnchangedWatermark_ServesMemoisedHash_WithoutReflattening()
    {
        var probe = CreateProbe();

        Assert.Equal("sha256:first", await probe.GetCurrentRevisionHashAsync(InstanceId));
        Assert.Equal(1, _flattenCount);

        Assert.Equal("sha256:first", await probe.GetCurrentRevisionHashAsync(InstanceId));
        Assert.Equal(1, _flattenCount);
    }

    /// <summary>
    /// <c>BumpAll</c> is the unattributed fallback used by everything that changes a
    /// flattening input without an owning template id — data-connection saves and
    /// bundle imports both rely on it. It must invalidate the memo.
    /// </summary>
    [Fact]
    public async Task BumpAll_InvalidatesTheMemo()
    {
        var probe = CreateProbe();
        await probe.GetCurrentRevisionHashAsync(InstanceId);

        // The underlying config drifted (e.g. a data connection was repointed).
        _currentHash = "sha256:second";

        // Without a bump the stale hash would still be served...
        Assert.Equal("sha256:first", await probe.GetCurrentRevisionHashAsync(InstanceId));

        _watermark.BumpAll();

        Assert.Equal("sha256:second", await probe.GetCurrentRevisionHashAsync(InstanceId));
        Assert.Equal(2, _flattenCount);
    }

    [Fact]
    public async Task BumpInstance_InvalidatesTheMemoForThatInstance()
    {
        var probe = CreateProbe();
        await probe.GetCurrentRevisionHashAsync(InstanceId);

        _currentHash = "sha256:second";
        _watermark.BumpInstance(InstanceId);

        Assert.Equal("sha256:second", await probe.GetCurrentRevisionHashAsync(InstanceId));
    }
}
