using FluentAssertions;
using Xunit;
using ZB.MOM.WW.LmxProxy.Client.Domain;
using ZB.MOM.WW.LmxProxy.Client.Tests.Fakes;

namespace ZB.MOM.WW.LmxProxy.Client.Tests;

public class LmxProxyClientSubscriptionTests
{
    [Fact]
    public async Task SubscribeAsync_InvokesCallbackForEachUpdate()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.SubscriptionMessages =
        [
            new VtqMessage
            {
                Tag = "Tag1",
                Value = new TypedValue { DoubleValue = 1.0 },
                TimestampUtcTicks = DateTime.UtcNow.Ticks,
                Quality = new QualityCode { StatusCode = 0x00000000 }
            },
            new VtqMessage
            {
                Tag = "Tag2",
                Value = new TypedValue { Int32Value = 42 },
                TimestampUtcTicks = DateTime.UtcNow.Ticks,
                Quality = new QualityCode { StatusCode = 0x00000000 }
            }
        ];

        var updates = new List<(string Tag, Vtq Vtq)>();
        var subscription = await client.SubscribeAsync(
            ["Tag1", "Tag2"],
            (tag, vtq) => updates.Add((tag, vtq)));

        // Wait for processing to complete (fake yields all then stops)
        await Task.Delay(500);

        updates.Should().HaveCount(2);
        updates[0].Tag.Should().Be("Tag1");
        updates[0].Vtq.Value.Should().Be(1.0);
        updates[1].Tag.Should().Be("Tag2");
        updates[1].Vtq.Value.Should().Be(42);

        subscription.Dispose();
        client.Dispose();
    }

    [Fact]
    public async Task SubscribeAsync_InvokesStreamErrorOnFailure()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.SubscriptionException = new InvalidOperationException("Stream broke");

        Exception? capturedError = null;
        var subscription = await client.SubscribeAsync(
            ["Tag1"],
            (_, _) => { },
            ex => capturedError = ex);

        // Wait for error to propagate
        await Task.Delay(500);

        capturedError.Should().NotBeNull();
        capturedError.Should().BeOfType<InvalidOperationException>();
        capturedError!.Message.Should().Be("Stream broke");

        subscription.Dispose();
        client.Dispose();
    }

    [Fact]
    public async Task SubscribeAsync_DisposeStopsProcessing()
    {
        var (client, fake) = TestableClient.CreateConnected();
        // Provide many messages but we'll dispose early
        fake.SubscriptionMessages =
        [
            new VtqMessage
            {
                Tag = "Tag1",
                Value = new TypedValue { DoubleValue = 1.0 },
                TimestampUtcTicks = DateTime.UtcNow.Ticks,
                Quality = new QualityCode { StatusCode = 0x00000000 }
            }
        ];

        var updates = new List<(string Tag, Vtq Vtq)>();
        var subscription = await client.SubscribeAsync(
            ["Tag1"],
            (tag, vtq) => updates.Add((tag, vtq)));

        // Dispose immediately
        subscription.Dispose();

        // Should not throw
        client.Dispose();
    }
}
