using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;

namespace ZB.MOM.WW.OtOpcUa.Server.Tests;

/// <summary>
///     Integration-style tests for the Phase 6.1 Stream D consumption hook — they don't touch
///     SQL Server (the real SealedBootstrap does, via sp_GetCurrentGenerationForCluster), but
///     they exercise ResilientConfigReader + GenerationSealedCache + StaleConfigFlag end-to-end
///     by simulating central-DB outcomes through a direct ReadAsync call.
/// </summary>
[Trait("Category", "Integration")]
public sealed class SealedBootstrapIntegrationTests : IDisposable
{
    private readonly string _root = Path.Combine(Path.GetTempPath(), $"otopcua-sealed-bootstrap-{Guid.NewGuid():N}");

    public void Dispose()
    {
        try
        {
            if (!Directory.Exists(_root)) return;
            foreach (var f in Directory.EnumerateFiles(_root, "*", SearchOption.AllDirectories))
                File.SetAttributes(f, FileAttributes.Normal);
            Directory.Delete(_root, recursive: true);
        }
        catch { /* best-effort */ }
    }

    [Fact]
    public async Task CentralDbSuccess_SealsSnapshot_And_FlagFresh()
    {
        var cache = new GenerationSealedCache(_root);
        var flag = new StaleConfigFlag();
        var reader = new ResilientConfigReader(cache, flag, NullLogger<ResilientConfigReader>.Instance,
            timeout: TimeSpan.FromSeconds(10));

        // Simulate the SealedBootstrap fresh-path: central DB returns generation id 42; the
        // bootstrap seals it + ResilientConfigReader marks the flag fresh.
        var result = await reader.ReadAsync(
            "c-a",
            centralFetch: async _ =>
            {
                await cache.SealAsync(new GenerationSnapshot
                {
                    ClusterId = "c-a",
                    GenerationId = 42,
                    CachedAt = DateTime.UtcNow,
                    PayloadJson = "{\"gen\":42}",
                }, CancellationToken.None);
                return (long?)42;
            },
            fromSnapshot: snap => (long?)snap.GenerationId,
            CancellationToken.None);

        result.ShouldBe(42);
        flag.IsStale.ShouldBeFalse();
        cache.TryGetCurrentGenerationId("c-a").ShouldBe(42);
    }

    [Fact]
    public async Task CentralDbFails_FallsBackToSealedSnapshot_FlagStale()
    {
        var cache = new GenerationSealedCache(_root);
        var flag = new StaleConfigFlag();
        var reader = new ResilientConfigReader(cache, flag, NullLogger<ResilientConfigReader>.Instance,
            timeout: TimeSpan.FromSeconds(10), retryCount: 0);

        // Seed a prior sealed snapshot (simulating a previous successful boot).
        await cache.SealAsync(new GenerationSnapshot
        {
            ClusterId = "c-a", GenerationId = 37, CachedAt = DateTime.UtcNow,
            PayloadJson = "{\"gen\":37}",
        });

        // Now simulate central DB down → fallback.
        var result = await reader.ReadAsync(
            "c-a",
            centralFetch: _ => throw new InvalidOperationException("SQL dead"),
            fromSnapshot: snap => (long?)snap.GenerationId,
            CancellationToken.None);

        result.ShouldBe(37);
        flag.IsStale.ShouldBeTrue("cache fallback flips the /healthz flag");
    }

    [Fact]
    public async Task NoSnapshot_AndCentralDown_Throws_ClearError()
    {
        var cache = new GenerationSealedCache(_root);
        var flag = new StaleConfigFlag();
        var reader = new ResilientConfigReader(cache, flag, NullLogger<ResilientConfigReader>.Instance,
            timeout: TimeSpan.FromSeconds(10), retryCount: 0);

        await Should.ThrowAsync<GenerationCacheUnavailableException>(async () =>
        {
            await reader.ReadAsync<long?>(
                "c-a",
                centralFetch: _ => throw new InvalidOperationException("SQL dead"),
                fromSnapshot: snap => (long?)snap.GenerationId,
                CancellationToken.None);
        });
    }

    [Fact]
    public async Task SuccessfulBootstrap_AfterFailure_ClearsStaleFlag()
    {
        var cache = new GenerationSealedCache(_root);
        var flag = new StaleConfigFlag();
        var reader = new ResilientConfigReader(cache, flag, NullLogger<ResilientConfigReader>.Instance,
            timeout: TimeSpan.FromSeconds(10), retryCount: 0);

        await cache.SealAsync(new GenerationSnapshot
        {
            ClusterId = "c-a", GenerationId = 1, CachedAt = DateTime.UtcNow, PayloadJson = "{}",
        });

        // Fallback serves snapshot → flag goes stale.
        await reader.ReadAsync("c-a",
            centralFetch: _ => throw new InvalidOperationException("dead"),
            fromSnapshot: s => (long?)s.GenerationId,
            CancellationToken.None);
        flag.IsStale.ShouldBeTrue();

        // Subsequent successful bootstrap clears it.
        await reader.ReadAsync("c-a",
            centralFetch: _ => ValueTask.FromResult((long?)5),
            fromSnapshot: s => (long?)s.GenerationId,
            CancellationToken.None);
        flag.IsStale.ShouldBeFalse("next successful DB round-trip clears the flag");
    }
}
