using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.AuditLog.Central;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using SiteEntity = ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites.Site;

namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Central;

/// <summary>
/// Unit tests for the production <see cref="SiteEnumerator"/> — the central
/// reconciliation-singleton collaborator that projects the config-DB
/// <see cref="SiteEntity"/> rows into the <see cref="SiteEntry"/> targets the
/// <see cref="SiteAuditReconciliationActor"/> polls.
/// </summary>
/// <remarks>
/// The enumerator opens a fresh DI scope per <see cref="SiteEnumerator.EnumerateAsync"/>
/// call (mirroring the per-tick scope pattern in the reconciliation actor)
/// because <see cref="ISiteRepository"/> is a SCOPED EF Core service. The tests
/// register a substituted repository as a scoped service so the enumerator's
/// <c>CreateAsyncScope</c> resolves it and the projection / blank-address
/// filtering can be exercised without an MSSQL container.
/// </remarks>
public class SiteEnumeratorTests
{
    private static SiteEntity SiteWith(string identifier, string? grpcNodeA, string? grpcNodeB = null)
    {
        var site = new SiteEntity($"Display {identifier}", identifier)
        {
            GrpcNodeAAddress = grpcNodeA,
            GrpcNodeBAddress = grpcNodeB,
        };
        return site;
    }

    private static IServiceProvider BuildProvider(ISiteRepository repository)
    {
        var services = new ServiceCollection();
        // Scoped to match the production lifetime (EF Core); the enumerator
        // must open a scope to resolve it.
        services.AddScoped(_ => repository);
        return services.BuildServiceProvider();
    }

    [Fact]
    public async Task EnumerateAsync_ProjectsSitesWithNodeAAddress_AndSkipsBlankOnes()
    {
        var repository = Substitute.For<ISiteRepository>();
        repository.GetAllSitesAsync(Arg.Any<CancellationToken>()).Returns(new List<SiteEntity>
        {
            SiteWith("site-a", "http://site-a:8083"),
            SiteWith("site-b", grpcNodeA: "   "), // blank NodeA -> skipped
        });

        var enumerator = new SiteEnumerator(BuildProvider(repository));

        var result = await enumerator.EnumerateAsync();

        var entry = Assert.Single(result);
        Assert.Equal("site-a", entry.SiteId);
        Assert.Equal("http://site-a:8083", entry.GrpcEndpoint);
    }

    [Fact]
    public async Task EnumerateAsync_SkipsNullNodeAAddress()
    {
        var repository = Substitute.For<ISiteRepository>();
        repository.GetAllSitesAsync(Arg.Any<CancellationToken>()).Returns(new List<SiteEntity>
        {
            SiteWith("site-null", grpcNodeA: null),
        });

        var enumerator = new SiteEnumerator(BuildProvider(repository));

        var result = await enumerator.EnumerateAsync();

        Assert.Empty(result);
    }

    [Fact]
    public async Task EnumerateAsync_BothAddresses_ProjectsNodeBAsFallback()
    {
        // Task 18: a site with both NodeA and a DISTINCT NodeB yields
        // SiteEntry(primary=NodeA, FallbackGrpcEndpoint=NodeB).
        var repository = Substitute.For<ISiteRepository>();
        repository.GetAllSitesAsync(Arg.Any<CancellationToken>()).Returns(new List<SiteEntity>
        {
            SiteWith("site-ab", grpcNodeA: "http://site-a:8083", grpcNodeB: "http://site-b:8083"),
        });

        var enumerator = new SiteEnumerator(BuildProvider(repository));

        var entry = Assert.Single(await enumerator.EnumerateAsync());
        Assert.Equal("site-ab", entry.SiteId);
        Assert.Equal("http://site-a:8083", entry.GrpcEndpoint);
        Assert.Equal("http://site-b:8083", entry.FallbackGrpcEndpoint);
    }

    [Fact]
    public async Task EnumerateAsync_OnlyNodeB_IsNotSkipped_AndHasNoFallback()
    {
        // Task 18: a site with a blank NodeA but a valid NodeB is no longer
        // skipped — NodeB becomes the primary and there is no separate fallback.
        var repository = Substitute.For<ISiteRepository>();
        repository.GetAllSitesAsync(Arg.Any<CancellationToken>()).Returns(new List<SiteEntity>
        {
            SiteWith("site-bonly", grpcNodeA: "   ", grpcNodeB: "http://site-b:8083"),
        });

        var enumerator = new SiteEnumerator(BuildProvider(repository));

        var entry = Assert.Single(await enumerator.EnumerateAsync());
        Assert.Equal("site-bonly", entry.SiteId);
        Assert.Equal("http://site-b:8083", entry.GrpcEndpoint);
        Assert.Null(entry.FallbackGrpcEndpoint);
    }

    [Fact]
    public async Task EnumerateAsync_ReturnsEmpty_WhenNoSites()
    {
        var repository = Substitute.For<ISiteRepository>();
        repository.GetAllSitesAsync(Arg.Any<CancellationToken>()).Returns(new List<SiteEntity>());

        var enumerator = new SiteEnumerator(BuildProvider(repository));

        var result = await enumerator.EnumerateAsync();

        Assert.Empty(result);
    }
}
