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 LmxProxyClientReadWriteTests
{
    [Fact]
    public async Task ReadAsync_ReturnsVtqFromResponse()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.ReadResponseToReturn = new ReadResponse
        {
            Success = true,
            Vtq = new VtqMessage
            {
                Tag = "TestTag",
                Value = new TypedValue { DoubleValue = 42.5 },
                TimestampUtcTicks = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks,
                Quality = new QualityCode { StatusCode = 0x00000000 }
            }
        };

        var result = await client.ReadAsync("TestTag");

        result.Value.Should().Be(42.5);
        result.Quality.Should().Be(Quality.Good);
        fake.ReadCalls.Should().HaveCount(1);
        fake.ReadCalls[0].Tag.Should().Be("TestTag");
        fake.ReadCalls[0].SessionId.Should().Be("test-session-123");
        client.Dispose();
    }

    [Fact]
    public async Task ReadAsync_ThrowsOnFailureResponse()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.ReadResponseToReturn = new ReadResponse { Success = false, Message = "Tag not found" };

        var act = () => client.ReadAsync("BadTag");

        await act.Should().ThrowAsync<InvalidOperationException>()
            .WithMessage("*Tag not found*");
        client.Dispose();
    }

    [Fact]
    public async Task ReadAsync_ThrowsWhenNotConnected()
    {
        var client = new LmxProxyClient("localhost", 50051, null, null);

        var act = () => client.ReadAsync("AnyTag");

        await act.Should().ThrowAsync<InvalidOperationException>()
            .WithMessage("*not connected*");
        client.Dispose();
    }

    [Fact]
    public async Task ReadBatchAsync_ReturnsDictionaryOfVtqs()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.ReadBatchResponseToReturn = new ReadBatchResponse
        {
            Success = true,
            Vtqs =
            [
                new VtqMessage
                {
                    Tag = "Tag1",
                    Value = new TypedValue { Int32Value = 100 },
                    TimestampUtcTicks = DateTime.UtcNow.Ticks,
                    Quality = new QualityCode { StatusCode = 0x00000000 }
                },
                new VtqMessage
                {
                    Tag = "Tag2",
                    Value = new TypedValue { BoolValue = true },
                    TimestampUtcTicks = DateTime.UtcNow.Ticks,
                    Quality = new QualityCode { StatusCode = 0x00000000 }
                }
            ]
        };

        var result = await client.ReadBatchAsync(["Tag1", "Tag2"]);

        result.Should().HaveCount(2);
        result["Tag1"].Value.Should().Be(100);
        result["Tag2"].Value.Should().Be(true);
        client.Dispose();
    }

    [Fact]
    public async Task WriteAsync_SendsTypedValueDirectly()
    {
        var (client, fake) = TestableClient.CreateConnected();
        var typedValue = new TypedValue { DoubleValue = 99.9 };

        await client.WriteAsync("TestTag", typedValue);

        fake.WriteCalls.Should().HaveCount(1);
        fake.WriteCalls[0].Tag.Should().Be("TestTag");
        fake.WriteCalls[0].Value.Should().NotBeNull();
        fake.WriteCalls[0].Value!.DoubleValue.Should().Be(99.9);
        client.Dispose();
    }

    [Fact]
    public async Task WriteAsync_ThrowsOnFailureResponse()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.WriteResponseToReturn = new WriteResponse { Success = false, Message = "Write error" };

        var act = () => client.WriteAsync("Tag", new TypedValue { Int32Value = 1 });

        await act.Should().ThrowAsync<InvalidOperationException>()
            .WithMessage("*Write error*");
        client.Dispose();
    }

    [Fact]
    public async Task WriteBatchAsync_SendsAllItems()
    {
        var (client, fake) = TestableClient.CreateConnected();
        var values = new Dictionary<string, TypedValue>
        {
            ["Tag1"] = new TypedValue { DoubleValue = 1.0 },
            ["Tag2"] = new TypedValue { Int32Value = 2 },
            ["Tag3"] = new TypedValue { BoolValue = true }
        };

        await client.WriteBatchAsync(values);

        fake.WriteBatchCalls.Should().HaveCount(1);
        fake.WriteBatchCalls[0].Items.Should().HaveCount(3);
        client.Dispose();
    }

    [Fact]
    public async Task WriteBatchAndWaitAsync_ReturnsResponse()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.WriteBatchAndWaitResponseToReturn = new WriteBatchAndWaitResponse
        {
            Success = true,
            FlagReached = true,
            ElapsedMs = 150,
            WriteResults = [new WriteResult { Tag = "Tag1", Success = true }]
        };
        var values = new Dictionary<string, TypedValue>
        {
            ["Tag1"] = new TypedValue { Int32Value = 1 }
        };

        var result = await client.WriteBatchAndWaitAsync(
            values, "FlagTag", new TypedValue { BoolValue = true });

        result.FlagReached.Should().BeTrue();
        result.ElapsedMs.Should().Be(150);
        client.Dispose();
    }

    [Fact]
    public async Task CheckApiKeyAsync_ReturnsApiKeyInfo()
    {
        var (client, fake) = TestableClient.CreateConnected();
        fake.CheckApiKeyResponseToReturn = new CheckApiKeyResponse { IsValid = true, Message = "Admin key" };

        var result = await client.CheckApiKeyAsync("my-api-key");

        result.IsValid.Should().BeTrue();
        result.Description.Should().Be("Admin key");
        client.Dispose();
    }
}
