using NATS.Server.JetStream.MirrorSource;
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Storage;
using Shouldly;

namespace NATS.Server.JetStream.Tests.JetStream.Streams;

// Go reference: server/stream.go:2739-2743 (mirrorInfo building StreamSourceInfo)
// Go reference: server/stream.go:2687-2736 (sourcesInfo / StreamSourceInfo)

public class SourceMirrorInfoTests
{
    // -------------------------------------------------------------------------
    // MirrorInfoResponse tests
    // -------------------------------------------------------------------------

    [Fact]
    // Go reference: server/stream.go:2739 — StreamSourceInfo.Name field
    public async Task MirrorInfoResponse_has_correct_name()
    {
        var target = new MemStore();
        await using var mirror = new MirrorCoordinator(target);

        var info = mirror.GetMirrorInfo("MIRROR");

        info.Name.ShouldBe("MIRROR");
    }

    [Fact]
    // Go reference: server/stream.go:2740 — StreamSourceInfo.Lag field from mirrorInfo
    public async Task MirrorInfoResponse_shows_lag()
    {
        var target = new MemStore();
        await using var mirror = new MirrorCoordinator(target);

        // Sync up to origin seq 5, origin is at 10 → lag = 5 via GetHealthReport
        await mirror.OnOriginAppendAsync(MakeMessage(5, "a", "data"), default);
        var report = mirror.GetHealthReport(originLastSeq: 10);
        report.Lag.ShouldBe(5UL);

        // GetMirrorInfo calls GetHealthReport() without originLastSeq, so Lag comes from
        // the internal Lag property (0 after in-process sync). Validate the field is wired up.
        var info = mirror.GetMirrorInfo("LAGGED");
        info.Lag.ShouldBe(mirror.Lag);
        info.Name.ShouldBe("LAGGED");
    }

    [Fact]
    // Go reference: server/stream.go:2741 — Active field, -1 when never synced
    public async Task MirrorInfoResponse_active_is_negative_when_never_synced()
    {
        var target = new MemStore();
        await using var mirror = new MirrorCoordinator(target);

        var info = mirror.GetMirrorInfo("FRESH");

        info.Active.ShouldBe(-1L);
    }

    [Fact]
    // Go reference: server/stream.go:2741 — Active = ms since last sync
    public async Task MirrorInfoResponse_active_shows_ms_since_sync()
    {
        var target = new MemStore();
        await using var mirror = new MirrorCoordinator(target);

        await mirror.OnOriginAppendAsync(MakeMessage(1, "a", "payload"), default);

        var info = mirror.GetMirrorInfo("SYNCED");

        info.Active.ShouldBeGreaterThanOrEqualTo(0L);
    }

    [Fact]
    // Go reference: server/stream.go:2742 — StreamSourceInfo.Error field
    public async Task MirrorInfoResponse_includes_error()
    {
        var target = new MemStore();
        await using var mirror = new MirrorCoordinator(target);

        mirror.SetError("consumer not found");

        var info = mirror.GetMirrorInfo("BROKEN");

        info.Error.ShouldBe("consumer not found");
    }

    // -------------------------------------------------------------------------
    // SourceInfoResponse tests
    // -------------------------------------------------------------------------

    [Fact]
    // Go reference: server/stream.go:2698 — StreamSourceInfo.Name from sourceInfo
    public async Task SourceInfoResponse_has_correct_name()
    {
        var target = new MemStore();
        await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "MY_SOURCE" });

        var info = source.GetSourceInfo();

        info.Name.ShouldBe("MY_SOURCE");
    }

    [Fact]
    // Go reference: server/stream.go:2700 — StreamSourceInfo.FilterSubject
    public async Task SourceInfoResponse_shows_filter_subject()
    {
        var target = new MemStore();
        await using var source = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SRC",
            FilterSubject = "orders.*",
        });

        var info = source.GetSourceInfo();

        info.FilterSubject.ShouldBe("orders.*");
    }

    [Fact]
    // Go reference: server/stream.go:2701 — Active field, -1 when never synced
    public async Task SourceInfoResponse_active_is_negative_when_never_synced()
    {
        var target = new MemStore();
        await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "SRC" });

        var info = source.GetSourceInfo();

        info.Active.ShouldBe(-1L);
    }

    [Fact]
    // Go reference: server/stream.go:2701 — Active = ms since last sync
    public async Task SourceInfoResponse_active_shows_ms_since_sync()
    {
        var target = new MemStore();
        await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "SRC" });

        await source.OnOriginAppendAsync(MakeMessage(1, "orders.created", "payload"), default);

        var info = source.GetSourceInfo();

        info.Active.ShouldBeGreaterThanOrEqualTo(0L);
    }

    [Fact]
    // Go reference: server/stream.go:2699 — StreamSourceInfo.Lag from sourceInfo
    public async Task SourceInfoResponse_lag_reflects_health_report()
    {
        var target = new MemStore();
        await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "SRC" });

        // Apply 3 messages directly (no background loop, no timing dependency).
        // Origin has 5 messages total; coordinator sees up to seq 3, so lag = 2.
        await source.OnOriginAppendAsync(MakeMessage(1, "a", "1"), default);
        await source.OnOriginAppendAsync(MakeMessage(2, "b", "2"), default);
        await source.OnOriginAppendAsync(MakeMessage(3, "c", "3"), default);

        // Simulate the lag that GetHealthReport would compute against a known origin end.
        var healthReport = source.GetHealthReport(originLastSeq: 5);
        var info = source.GetSourceInfo();

        // GetSourceInfo calls GetHealthReport() without originLastSeq, so both should
        // reflect the same internal Lag value (0 after in-process sync).
        info.Lag.ShouldBe(source.Lag);
        // Verify against the explicit-originLastSeq health report for the expected lag value.
        healthReport.Lag.ShouldBe(2UL);
    }

    // -------------------------------------------------------------------------
    // Helper
    // -------------------------------------------------------------------------

    private static StoredMessage MakeMessage(ulong seq, string subject, string payload) => new()
    {
        Sequence = seq,
        Subject = subject,
        Payload = System.Text.Encoding.UTF8.GetBytes(payload),
        TimestampUtc = DateTime.UtcNow,
    };
}
