using Shouldly;
using Xunit;

namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;

/// <summary>
///     Unit tests for <see cref="S7Driver"/>'s <c>IReadable</c>/<c>IWritable</c> surface
///     that don't require a live PLC — covers error paths (not-initialized, unknown tag,
///     read-only write rejection, unsupported data types). Wire-level round-trip tests
///     against a live S7 or a mock-server land in a follow-up PR since S7.Net doesn't ship
///     an in-process fake and an adequate mock is non-trivial.
/// </summary>
[Trait("Category", "Unit")]
public sealed class S7DriverReadWriteTests
{
    /// <summary>Verifies that initialization rejects invalid tag addresses and fails fast.</summary>
    [Fact]
    public async Task Initialize_rejects_invalid_tag_address_and_fails_fast()
    {
        // Bad address at init time must throw; the alternative (deferring the parse to the
        // first read) would surface the config bug as BadInternalError on every subsequent
        // Read which is impossible for an operator to diagnose from the OPC UA client.
        var opts = new S7DriverOptions
        {
            Host = "192.0.2.1", // reserved — will never complete TCP handshake
            Timeout = TimeSpan.FromMilliseconds(250),
            RawTags = S7RawTags.Entries(new S7TagDefinition("BadTag", "NOT-AN-S7-ADDRESS", S7DataType.Int16)),
        };
        using var drv = new S7Driver(opts, "s7-bad-tag");

        // Either the TCP connect fails first (Exception) or the parser fails (FormatException)
        // — both are acceptable since both are init-time fail-fast. What matters is that we
        // don't return a "healthy" driver with a latent bad tag.
        await Should.ThrowAsync<Exception>(async () =>
            await drv.InitializeAsync("{}", TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies that ReadAsync without initialize throws InvalidOperationException.</summary>
    [Fact]
    public async Task ReadAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new S7Driver(new S7DriverOptions { Host = "192.0.2.1" }, "s7-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.ReadAsync(["Any"], TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies that WriteAsync without initialize throws InvalidOperationException.</summary>
    [Fact]
    public async Task WriteAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new S7Driver(new S7DriverOptions { Host = "192.0.2.1" }, "s7-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.WriteAsync(
                [new(FullReference: "Any", Value: (short)0)],
                TestContext.Current.CancellationToken));
    }
}
