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

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

/// <summary>
///     Base for every FOCAS CLI command. Carries the CNC endpoint options
///     (host / port / series) + exposes <see cref="BuildOptions"/> so each command
///     can synthesise a <see cref="FocasDriverOptions"/> with one device + one tag.
/// </summary>
public abstract class FocasCommandBase : DriverCommandBase
{
    /// <summary>Gets the CNC IP address or hostname.</summary>
    [CommandOption("cnc-host", 'h', Description =
        "CNC IP address or hostname. FOCAS-over-EIP listens on port 8193 by default.",
        IsRequired = true)]
    public string CncHost { get; init; } = default!;

    /// <summary>Gets the FOCAS TCP port.</summary>
    [CommandOption("cnc-port", 'p', Description = "FOCAS TCP port (default 8193).")]
    public int CncPort { get; init; } = 8193;

    /// <summary>Gets the CNC series.</summary>
    [CommandOption("series", 's', Description =
        "CNC series: Unknown / Zero_i_D / Zero_i_F / Zero_i_MF / Zero_i_TF / Sixteen_i / " +
        "Thirty_i / ThirtyOne_i / ThirtyTwo_i / PowerMotion_i (default Unknown).")]
    public FocasCncSeries Series { get; init; } = FocasCncSeries.Unknown;

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

    /// <inheritdoc />
    public override TimeSpan Timeout
    {
        get => TimeSpan.FromMilliseconds(TimeoutMs);
        init { /* driven by TimeoutMs */ }
    }

    /// <summary>Canonical FOCAS host-address string, shape <c>focas://host:port</c>.</summary>
    protected string HostAddress => $"focas://{CncHost}:{CncPort}";

    /// <summary>
    ///     Build a <see cref="FocasDriverOptions"/> with the CNC target this base collected
    ///     + the tag list a subclass supplies. Probe disabled; the driver's default managed
    ///     wire client opens a TCP:8193 session to the CNC and surfaces unreachable endpoints
    ///     as <c>BadCommunicationError</c>.
    /// </summary>
    /// <param name="tags">The tag definitions to include in the driver options.</param>
    /// <returns>A <see cref="FocasDriverOptions"/> configured with a single device and the supplied tags.</returns>
    protected FocasDriverOptions BuildOptions(IReadOnlyList<FocasTagDefinition> tags) => new()
    {
        Devices = [new FocasDeviceOptions(
            HostAddress: HostAddress,
            DeviceName: $"cli-{CncHost}:{CncPort}",
            Series: Series)],
        // v3: turn each typed def into the RawTagEntry delivery unit — TagConfig blob via
        // FocasTagDefinitionFactory.ToTagConfig, keyed by the def's synthesised name (RawPath), routed to
        // the device by the def's DeviceHostAddress (which the driver matches against Devices).
        RawTags = [.. tags.Select(t => new RawTagEntry(
            t.Name, FocasTagDefinitionFactory.ToTagConfig(t), t.WriteIdempotent, t.DeviceHostAddress))],
        Timeout = Timeout,
        Probe = new FocasProbeOptions { Enabled = false },
    };

    /// <summary>Gets the driver instance ID.</summary>
    protected string DriverInstanceId => $"focas-cli-{CncHost}:{CncPort}";

    /// <summary>
    ///     Validates numeric option ranges at the CLI boundary so a
    ///     zero/negative <c>--cnc-port</c>, <c>--timeout-ms</c>, or <c>--interval-ms</c>
    ///     surfaces as a clean <see cref="CliFx.Exceptions.CommandException"/> rather than
    ///     either an opaque downstream exception (invalid <c>focas://host:&lt;n&gt;</c> /
    ///     zero <c>TimeSpan</c>) or a tight-spinning poll loop. The <c>--interval-ms</c>
    ///     option is subscribe-only — pass <c>null</c> for probe/read/write so this
    ///     helper can be a single shared validator.
    /// </summary>
    /// <param name="intervalMs">The interval in milliseconds, or null if not applicable.</param>
    protected void ValidateOptions(int? intervalMs = null)
    {
        if (CncPort < 1 || CncPort > 65535)
            throw new CliFx.Exceptions.CommandException(
                $"--cnc-port must be in the range 1..65535 (got {CncPort}).");
        if (TimeoutMs <= 0)
            throw new CliFx.Exceptions.CommandException(
                $"--timeout-ms must be positive (got {TimeoutMs}).");
        if (intervalMs is { } iv && iv <= 0)
            throw new CliFx.Exceptions.CommandException(
                $"--interval-ms must be positive (got {iv}).");
    }
}
