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

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

/// <summary>
///     Base for every AB CIP CLI command. Carries the libplctag endpoint options
///     (<c>--gateway</c> + <c>--family</c>) and exposes <see cref="BuildOptions"/> so each
///     command can synthesise an <see cref="AbCipDriverOptions"/> from CLI flags + its own
///     tag list.
/// </summary>
public abstract class AbCipCommandBase : DriverCommandBase
{
    /// <summary>Gets the canonical AB CIP gateway address.</summary>
    [CommandOption("gateway", 'g', Description =
        "Canonical AB CIP gateway: ab://host[:port]/cip-path. Port defaults to 44818 " +
        "(EtherNet/IP). cip-path is family-specific: ControlLogix / CompactLogix need " +
        "'1,0' to reach slot 0 of the CPU chassis; Micro800 takes an empty path; " +
        "GuardLogix typically '1,0' same as ControlLogix.",
        IsRequired = true)]
    public string Gateway { get; init; } = default!;

    /// <summary>Gets the PLC family type.</summary>
    [CommandOption("family", 'f', Description =
        "ControlLogix / CompactLogix / Micro800 / GuardLogix (default ControlLogix).")]
    public AbCipPlcFamily Family { get; init; } = AbCipPlcFamily.ControlLogix;

    /// <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 />
    public override TimeSpan Timeout
    {
        get
        {
            if (TimeoutMs <= 0)
                throw new CliFx.Exceptions.CommandException(
                    $"--timeout-ms must be > 0 (got {TimeoutMs}). " +
                    "Pick a positive number of milliseconds for the per-operation timeout.");
            return TimeSpan.FromMilliseconds(TimeoutMs);
        }
        init => throw new NotSupportedException(
            $"{nameof(AbCipCommandBase)}.{nameof(Timeout)} is derived from {nameof(TimeoutMs)} " +
            "and cannot be assigned directly. Set TimeoutMs instead.");
    }

    /// <summary>
    ///     Build an <see cref="AbCipDriverOptions"/> with the device + tag list a subclass
    ///     supplies. Probe + alarm projection are disabled — CLI runs are one-shot; the
    ///     probe loop would race the operator's own reads. Each typed tag is serialised to the v3
    ///     <see cref="RawTagEntry"/> shape (RawPath = tag name, TagConfig blob via
    ///     <see cref="AbCipTagDefinitionFactory.ToTagConfig"/>, DeviceName = the tag's device routing key)
    ///     — the same seam the deploy artifact uses.
    /// </summary>
    /// <param name="tags">The list of tag definitions to include in the options.</param>
    /// <returns>The constructed <see cref="AbCipDriverOptions"/>.</returns>
    protected AbCipDriverOptions BuildOptions(IReadOnlyList<AbCipTagDefinition> tags) => new()
    {
        Devices = [new AbCipDeviceOptions(
            HostAddress: Gateway,
            PlcFamily: Family,
            DeviceName: $"cli-{Family}")],
        RawTags = [.. tags.Select(t => new RawTagEntry(
            RawPath: t.Name,
            TagConfig: AbCipTagDefinitionFactory.ToTagConfig(t),
            WriteIdempotent: t.WriteIdempotent,
            DeviceName: t.DeviceHostAddress))],
        Timeout = Timeout,
        Probe = new AbCipProbeOptions { Enabled = false },
        EnableControllerBrowse = false,
        EnableAlarmProjection = false,
    };

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

    /// <summary>
    ///     Guards against <see cref="AbCipDataType.Structure"/> being passed to a command
    ///     that does not support UDT layouts. Call at the top of <c>ExecuteAsync</c> for any
    ///     command that accepts <c>--type</c> but cannot handle memberless Structure tags.
    ///     Throws a <see cref="CliFx.Exceptions.CommandException"/> if <paramref name="type"/>
    ///     is <see cref="AbCipDataType.Structure"/>.
    /// </summary>
    /// <param name="type">The data type to validate.</param>
    protected static void RejectStructure(AbCipDataType type)
    {
        if (type == AbCipDataType.Structure)
            throw new CliFx.Exceptions.CommandException(
                "Structure (UDT) reads are out of scope for this command — those need an explicit " +
                "member layout, which belongs in a real driver config.");
    }
}
