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

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

/// <summary>
///     Base for every TwinCAT CLI command. Carries the AMS target options
///     (<c>--ams-net-id</c> + <c>--ams-port</c>) + the per-call timeout. Commands that build
///     a single-device / single-tag <see cref="TwinCATDriverOptions"/> from flag input inherit
///     from <see cref="TwinCATTagCommandBase"/> instead — that intermediate adds the
///     <c>--poll-only</c> flag and the <c>BuildOptions</c> helper.
/// </summary>
public abstract class TwinCATCommandBase : DriverCommandBase
{
    /// <summary>Gets the AMS Net ID of the target runtime.</summary>
    [CommandOption("ams-net-id", 'n', Description =
        "AMS Net ID of the target runtime (e.g. '192.168.1.40.1.1' or '127.0.0.1.1.1' for local).",
        IsRequired = true)]
    public string AmsNetId { get; init; } = default!;

    /// <summary>Gets the AMS port number.</summary>
    [CommandOption("ams-port", 'p', Description =
        "AMS port. TwinCAT 3 PLC runtime defaults to 851; TwinCAT 2 uses 801.")]
    public int AmsPort { get; init; } = 851;

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

    /// <inheritdoc />
    // Projected from TimeoutMs. The CliFx init accessor required by the abstract base property
    // is intentionally a no-op: TimeoutMs is the only source of truth, so any value an init
    // initialiser supplies to this property directly is silently dropped. Do NOT add a backing
    // field "fixing" the empty body — it would diverge from TimeoutMs and the two would drift on
    // every refactor.
    public override TimeSpan Timeout
    {
        get => TimeSpan.FromMilliseconds(TimeoutMs);
        init { /* see comment above — driven by TimeoutMs */ }
    }

    /// <summary>
    ///     Gets the canonical TwinCAT gateway string the driver's <c>TwinCATAmsAddress.TryParse</c>
    ///     consumes — shape <c>ads://{AmsNetId}:{AmsPort}</c>.
    /// </summary>
    protected string Gateway => $"ads://{AmsNetId}:{AmsPort}";

    /// <summary>Gets the driver instance ID for this command.</summary>
    protected string DriverInstanceId => $"twincat-cli-{AmsNetId}:{AmsPort}";

    /// <summary>
    ///     Validates the numeric options every TwinCAT CLI command shares (timeout + AMS port).
    ///     Subclasses override and call <c>base.Validate()</c> first to add their own range
    ///     checks. Throwing here surfaces a clean CliFx one-line error before the driver gets
    ///     a chance to fail with an opaque transport error.
    /// </summary>
    protected virtual void Validate()
    {
        if (TimeoutMs <= 0)
            throw new CommandException(
                $"--timeout-ms must be greater than 0 (got {TimeoutMs}).");
        if (AmsPort is <= 0 or > 65535)
            throw new CommandException(
                $"--ams-port must be in the range 1..65535 (got {AmsPort}).");
    }

    // ---- Test hooks ----
    // Protected members are exposed to the test assembly through these internal accessors so the
    // test project can cover Gateway / DriverInstanceId composition + range validation without
    // needing reflection on every assertion.
    /// <summary>Gets the gateway string for testing.</summary>
    internal string GatewayForTest => Gateway;

    /// <summary>Gets the driver instance ID for testing.</summary>
    internal string DriverInstanceIdForTest => DriverInstanceId;

    /// <summary>Validates the command for testing.</summary>
    internal void ValidateForTest() => Validate();
}
