using CliFx.Attributes;
using CliFx.Infrastructure;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.S7;
using ZB.MOM.WW.OtOpcUa.Driver.S7.Cli;

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

/// <summary>
///     Covers <see cref="S7CommandBase.BuildOptions"/> — the pure, deterministic mapping
///     from the base's host/port/CPU/rack/slot/timeout flags onto an
///     <c>S7DriverOptions</c>. The CLI is one-shot so the background connectivity probe
///     must be disabled.
/// </summary>
[Trait("Category", "Unit")]
public sealed class S7CommandBaseBuildOptionsTests
{
    // Test-only S7CommandBase concrete subclass that exposes the protected BuildOptions
    // helper. The [Command] attribute is required by the CliFx analyzer
    // (CliFx_CommandMustBeAnnotated) — this command is never registered with the CLI app
    // but the analyzer rule fires for every ICommand implementor in the compilation.
    /// <summary>Test-only S7CommandBase concrete subclass that exposes the protected BuildOptions helper.</summary>
    [Command("noop-test", Description = "Test-only probe of S7CommandBase.BuildOptions.")]
    private sealed class ProbeOnly : S7CommandBase
    {
        /// <inheritdoc />
        public override ValueTask ExecuteAsync(IConsole console) => default;

        /// <summary>Invokes the BuildOptions method with the given tags.</summary>
        /// <param name="tags">The tag definitions to pass to BuildOptions.</param>
        /// <returns>The resulting S7DriverOptions.</returns>
        public S7DriverOptions Invoke(IReadOnlyList<S7TagDefinition> tags) => BuildOptions(tags);
    }

    /// <summary>Verifies that BuildOptions disables probe for one-shot CLI runs.</summary>
    [Fact]
    public void BuildOptions_disables_probe_for_one_shot_cli_runs()
    {
        var sut = new ProbeOnly
        {
            Host = "10.0.0.5",
            Port = 102,
            CpuType = S7CpuType.S71500,
            Rack = 0,
            Slot = 0,
            TimeoutMs = 5000,
        };

        var options = sut.Invoke([]);

        options.Probe.ShouldNotBeNull();
        options.Probe.Enabled.ShouldBeFalse();
    }

    /// <summary>Verifies that BuildOptions maps TimeoutMs to Timeout TimeSpan.</summary>
    [Fact]
    public void BuildOptions_maps_TimeoutMs_to_Timeout_TimeSpan()
    {
        var sut = new ProbeOnly { Host = "h", TimeoutMs = 7500 };

        var options = sut.Invoke([]);

        options.Timeout.ShouldBe(TimeSpan.FromMilliseconds(7500));
    }

    /// <summary>Verifies that BuildOptions flows host, port, cpu, rack, and slot through.</summary>
    [Fact]
    public void BuildOptions_flows_host_port_cpu_rack_slot_through()
    {
        var sut = new ProbeOnly
        {
            Host = "plc.shop.local",
            Port = 4102,
            CpuType = S7CpuType.S7300,
            Rack = 1,
            Slot = 2,
            TimeoutMs = 3000,
        };

        var options = sut.Invoke([]);

        options.Host.ShouldBe("plc.shop.local");
        options.Port.ShouldBe(4102);
        options.CpuType.ShouldBe(S7CpuType.S7300);
        options.Rack.ShouldBe((short)1);
        options.Slot.ShouldBe((short)2);
    }

    /// <summary>Verifies that BuildOptions serialises each typed tag to a RawTagEntry (RawPath =
    /// the def's Name) whose TagConfig round-trips back to the same definition (v3 delivery shape).</summary>
    [Fact]
    public void BuildOptions_serialises_tags_to_raw_tag_entries()
    {
        var sut = new ProbeOnly { Host = "h" };
        var tag = new S7TagDefinition("t", "MW0", S7DataType.Int16, Writable: false);

        var options = sut.Invoke([tag]);

        options.RawTags.Count.ShouldBe(1);
        var entry = options.RawTags[0];
        entry.RawPath.ShouldBe("t");
        entry.WriteIdempotent.ShouldBe(tag.WriteIdempotent);

        S7TagDefinitionFactory.FromTagConfig(entry.TagConfig, entry.RawPath, out var round).ShouldBeTrue();
        round!.Name.ShouldBe("t");
        round.Address.ShouldBe("MW0");
        round.DataType.ShouldBe(S7DataType.Int16);
        round.Writable.ShouldBeFalse();
    }
}
