using CliFx.Attributes;
using CliFx.Exceptions;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;

namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli;

/// <summary>
///     Base for every Modbus CLI command. Carries the Modbus-TCP endpoint options
///     (host / port / unit-id) on top of <see cref="DriverCommandBase"/>'s verbose + timeout
///     + logging helpers, and exposes <see cref="BuildOptions"/> so each command can turn its
///     parsed flags into a <see cref="ModbusDriverOptions"/> ready to hand to the driver ctor.
/// </summary>
public abstract class ModbusCommandBase : DriverCommandBase
{
    /// <summary>Gets the Modbus-TCP server hostname or IP address.</summary>
    [CommandOption("host", 'h', Description = "Modbus-TCP server hostname or IP", IsRequired = true)]
    public string Host { get; init; } = default!;

    /// <summary>Gets the Modbus-TCP port number.</summary>
    [CommandOption("port", 'p', Description = "Modbus-TCP port (default 502)")]
    public int Port { get; init; } = 502;

    /// <summary>Gets the Modbus unit ID (slave ID).</summary>
    [CommandOption("unit-id", 'U', Description = "Modbus unit / slave ID (1-247, default 1)")]
    public byte UnitId { get; init; } = 1;

    /// <summary>Gets the per-PDU timeout in milliseconds.</summary>
    [CommandOption("timeout-ms", Description = "Per-PDU timeout in milliseconds (default 2000)")]
    public int TimeoutMs { get; init; } = 2000;

    /// <summary>Gets a value indicating whether to disable automatic reconnection.</summary>
    [CommandOption("disable-reconnect", Description =
        "Disable the built-in mid-transaction reconnect-and-retry. Matches the driver's " +
        "AutoReconnect=false setting — use when diagnosing socket teardown behaviour.")]
    public bool DisableAutoReconnect { get; init; }

    /// <inheritdoc />
    public override TimeSpan Timeout
    {
        get => TimeSpan.FromMilliseconds(TimeoutMs);
        init { /* driven by TimeoutMs property; setter required to satisfy base's init contract */ }
    }

    /// <summary>
    ///     Construct a <see cref="ModbusDriverOptions"/> with the endpoint fields this base
    ///     collected + whatever <paramref name="tags"/> the subclass declares. The CLI holds typed
    ///     definitions (synthesised from operator flags), so each is serialised back to a
    ///     <see cref="RawTagEntry"/> — the v3 delivery unit — via
    ///     <see cref="ModbusTagDefinitionFactory.ToTagConfig"/>, keyed by the def's synthesised name as
    ///     its RawPath. Probe is disabled — CLI runs are one-shot, the probe loop would race the
    ///     operator's command against its own keep-alive reads.
    /// </summary>
    /// <param name="tags">The tag definitions to include in the options.</param>
    /// <returns>A <see cref="ModbusDriverOptions"/> ready to hand to the driver constructor.</returns>
    protected ModbusDriverOptions BuildOptions(IReadOnlyList<ModbusTagDefinition> tags) => new()
    {
        Host = Host,
        Port = Port,
        UnitId = UnitId,
        Timeout = Timeout,
        AutoReconnect = !DisableAutoReconnect,
        RawTags = [.. tags.Select(t => new RawTagEntry(t.Name, ModbusTagDefinitionFactory.ToTagConfig(t), t.WriteIdempotent))],
        Probe = new ModbusProbeOptions { Enabled = false },
    };

    /// <summary>
    ///     Short instance id used in Serilog output so operators running the CLI against
    ///     multiple endpoints in parallel can distinguish the logs.
    /// </summary>
    protected string DriverInstanceId => $"modbus-cli-{Host}:{Port}";

    /// <summary>
    ///     Validate the endpoint flags at parse time so the operator
    ///     gets a clear CliFx error instead of an opaque socket / argument exception thrown
    ///     deep inside the driver. Ranges:
    ///     <list type="bullet">
    ///       <item><c>--port</c>: 1..65535 (IANA TCP port space, excludes the
    ///         "any" sentinel 0 and rejects negative / overflowed values).</item>
    ///       <item><c>--timeout-ms</c>: strictly positive — a non-positive
    ///         <see cref="TimeSpan"/> would propagate as an immediate-timeout into the
    ///         driver and surface as a confusing TimeoutException.</item>
    ///       <item><c>--unit-id</c>: 1..247 — the Modbus spec unicast unit-id range.
    ///         0 is the broadcast address and not valid for read/write requests; 248-255
    ///         are reserved. (Documented in <c>docs/Driver.Modbus.Cli.md</c>.)</item>
    ///     </list>
    /// </summary>
    protected void ValidateEndpoint()
    {
        if (Port < 1 || Port > 65535)
            throw new CommandException(
                $"--port must be in 1..65535 (got {Port}).");
        if (TimeoutMs <= 0)
            throw new CommandException(
                $"--timeout-ms must be strictly positive (got {TimeoutMs}).");
        if (UnitId < 1 || UnitId > 247)
            throw new CommandException(
                $"--unit-id must be in 1..247 per the Modbus spec (got {UnitId}); " +
                $"0 is the broadcast address, 248-255 are reserved.");
    }
}
