using System;
using System.Collections.Generic;
using CliFx.Attributes;
using CliFx.Infrastructure;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;

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

/// <summary>
///     Locks in <see cref="AbLegacyCommandBase.BuildOptions"/>: probe disabled,
///     device shape populated from <c>--gateway</c> + <c>--plc-type</c>, tag list
///     forwarded verbatim, and timeout propagated from <c>--timeout-ms</c>. A
///     regression here silently changes every AbLegacy CLI command's behaviour, so
///     covering it explicitly closes the test gap called out by finding
///     Driver.AbLegacy.Cli-007.
/// </summary>
[Trait("Category", "Unit")]
public sealed class BuildOptionsTests
{
    // Concrete subclass needed because AbLegacyCommandBase is abstract. Exposes the
    // protected BuildOptions via a public surface for the test.
    // [Command] satisfies CliFx's analyzer (ICommand subtypes must be annotated);
    // we never run it through CliFx, only invoke Build() directly.
    [Command("test-build-options")]
    private sealed class TestCommand : AbLegacyCommandBase
    {
        /// <summary>Builds driver options from the command's configuration and the provided tags.</summary>
        /// <param name="tags">The tag definitions to include in the options.</param>
        /// <returns>Configured driver options.</returns>
        public AbLegacyDriverOptions Build(IReadOnlyList<AbLegacyTagDefinition> tags)
            => BuildOptions(tags);

        /// <summary>Not used; this test command is for BuildOptions inspection only.</summary>
        /// <param name="console">Not used.</param>
        /// <returns>Not used.</returns>
        /// <exception cref="NotSupportedException">Always thrown; this method should not be called.</exception>
        public override System.Threading.Tasks.ValueTask ExecuteAsync(IConsole console)
            => throw new NotSupportedException("TestCommand is for BuildOptions inspection only.");
    }

    private static readonly IReadOnlyList<AbLegacyTagDefinition> SampleTags =
    [
        new AbLegacyTagDefinition(
            Name: "N7:0:Int",
            DeviceHostAddress: "ab://192.168.1.20/1,0",
            Address: "N7:0",
            DataType: AbLegacyDataType.Int,
            Writable: false),
        new AbLegacyTagDefinition(
            Name: "F8:0:Float",
            DeviceHostAddress: "ab://192.168.1.20/1,0",
            Address: "F8:0",
            DataType: AbLegacyDataType.Float,
            Writable: true),
    ];

    /// <summary>Verifies that probe is disabled for CLI one-shot runs.</summary>
    [Fact]
    public void BuildOptions_disables_probe_for_cli_oneshot_runs()
    {
        var cmd = new TestCommand
        {
            Gateway = "ab://192.168.1.20/1,0",
            PlcType = AbLegacyPlcFamily.Slc500,
            TimeoutMs = 5000,
        };

        var options = cmd.Build(SampleTags);

        options.Probe.ShouldNotBeNull();
        options.Probe.Enabled.ShouldBeFalse(
            "CLI commands are one-shot; the background probe loop is unwanted overhead.");
    }

    /// <summary>Verifies that device is populated from gateway and plc-type options.</summary>
    [Fact]
    public void BuildOptions_populates_single_device_from_gateway_and_plc_type()
    {
        var cmd = new TestCommand
        {
            Gateway = "ab://10.0.0.5/1,0",
            PlcType = AbLegacyPlcFamily.MicroLogix,
            TimeoutMs = 5000,
        };

        var options = cmd.Build(SampleTags);

        options.Devices.Count.ShouldBe(1);
        options.Devices[0].HostAddress.ShouldBe("ab://10.0.0.5/1,0");
        options.Devices[0].PlcFamily.ShouldBe(AbLegacyPlcFamily.MicroLogix);
        options.Devices[0].DeviceName.ShouldBe("cli-MicroLogix");
    }

    /// <summary>
    ///     Verifies that the typed tag list is delivered as v3 <see cref="RawTagEntry"/> records: RawPath =
    ///     the def's Name, DeviceName = the def's DeviceHostAddress, and the TagConfig blob round-trips the
    ///     def's address / dataType / writable through the real mapper.
    /// </summary>
    [Fact]
    public void BuildOptions_delivers_tags_as_rawtag_entries()
    {
        var cmd = new TestCommand
        {
            Gateway = "ab://192.168.1.20/1,0",
            PlcType = AbLegacyPlcFamily.Slc500,
            TimeoutMs = 5000,
        };

        var options = cmd.Build(SampleTags);

        options.RawTags.Count.ShouldBe(SampleTags.Count);
        for (var i = 0; i < SampleTags.Count; i++)
        {
            var def = SampleTags[i];
            var entry = options.RawTags[i];
            entry.RawPath.ShouldBe(def.Name);
            entry.DeviceName.ShouldBe(def.DeviceHostAddress);
            AbLegacyTagDefinitionFactory.FromTagConfig(entry.TagConfig, entry.RawPath, out var round).ShouldBeTrue();
            round.Address.ShouldBe(def.Address);
            round.DataType.ShouldBe(def.DataType);
            round.Writable.ShouldBe(def.Writable);
        }
    }

    /// <summary>Verifies that timeout-ms option is propagated to the driver options.</summary>
    [Fact]
    public void BuildOptions_propagates_timeout_ms()
    {
        var cmd = new TestCommand
        {
            Gateway = "ab://192.168.1.20/1,0",
            PlcType = AbLegacyPlcFamily.Slc500,
            TimeoutMs = 7500,
        };

        var options = cmd.Build(SampleTags);

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

    /// <summary>Verifies that empty tag list yields an empty tags collection in options.</summary>
    [Fact]
    public void BuildOptions_with_empty_tag_list_yields_empty_tags_collection()
    {
        var cmd = new TestCommand
        {
            Gateway = "ab://192.168.1.20/1,0",
            PlcType = AbLegacyPlcFamily.Plc5,
            TimeoutMs = 5000,
        };

        var options = cmd.Build([]);

        options.RawTags.ShouldBeEmpty();
        options.Devices.Count.ShouldBe(1);
        options.Devices[0].PlcFamily.ShouldBe(AbLegacyPlcFamily.Plc5);
    }
}
