using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using Serilog;

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

/// <summary>
///     Shared base for every driver test-client command (Modbus / AB CIP / AB Legacy / S7 /
///     TwinCAT / FOCAS). Carries the options that are meaningful regardless of protocol —
///     verbose logging + the standard timeout — plus helpers every command implementation wants:
///     Serilog configuration + cancellation-token capture.
/// </summary>
/// <remarks>
///     <para>
///         Each driver CLI sub-classes this with its own protocol-specific base (e.g.
///         <c>ModbusCommandBase</c>) that adds host/port/unit-id + a <c>BuildDriver()</c>
///         factory. That second layer is the point where the driver's <c>{Driver}DriverOptions</c>
///         type plugs in; keeping it out of this common base lets each driver CLI stay a thin
///         executable with no dependency on the other drivers' projects.
///     </para>
///     <para>
///         Why a shared base at all — without this every CLI re-authored the same ~40 lines
///         of Serilog wiring + cancel-token plumbing + verbose flag.
///     </para>
/// </remarks>
public abstract class DriverCommandBase : ICommand
{
    /// <summary>
    ///     Enable Serilog debug-level output. Leave off for clean one-line-per-call output;
    ///     switch on when diagnosing a connect / PDU-framing / retry problem.
    /// </summary>
    [CommandOption("verbose", Description = "Enable verbose/debug Serilog output")]
    public bool Verbose { get; init; }

    /// <summary>
    ///     Request-level timeout used by the driver's <c>Initialize</c> / <c>Read</c> /
    ///     <c>Write</c> / probe calls. Defaults per-protocol (Modbus: 2s, AB: 5s, S7: 5s,
    ///     TwinCAT: 5s) — each driver CLI overrides this property with the appropriate
    ///     <c>[CommandOption]</c> default.
    /// </summary>
    public abstract TimeSpan Timeout { get; init; }

    /// <summary>Executes the command with the given console output.</summary>
    /// <param name="console">The console for output.</param>
    /// <returns>A task that completes when the command is finished.</returns>
    public abstract ValueTask ExecuteAsync(IConsole console);

    /// <summary>
    ///     Configures the process-global Serilog logger. Intended to be called exactly once,
    ///     at the top of <see cref="ExecuteAsync"/>, so driver-internal <c>Log.Logger</c>
    ///     writes land on the same sink as the CLI's operator-facing output.
    ///     If the logger has already been configured this call is a no-op (idempotent).
    ///     Call <see cref="FlushLogging"/> in a <c>finally</c> block to ensure buffered output
    ///     is flushed before the process exits.
    /// </summary>
    protected void ConfigureLogging()
    {
        if (_loggingConfigured) return;
        _loggingConfigured = true;

        // Dispose the previous global logger (e.g. Serilog's silent bootstrap logger) so
        // its resources are released cleanly before we overwrite Log.Logger.
        var previous = Log.Logger;
        var config = new LoggerConfiguration();
        if (Verbose)
            config.MinimumLevel.Debug().WriteTo.Console();
        else
            config.MinimumLevel.Warning().WriteTo.Console();
        Log.Logger = config.CreateLogger();
        (previous as IDisposable)?.Dispose();
    }

    /// <summary>
    ///     Flushes and closes the Serilog logger configured by <see cref="ConfigureLogging"/>.
    ///     Call this in a <c>finally</c> block inside <see cref="ExecuteAsync"/> to prevent
    ///     buffered log output from being lost on process exit, particularly for long-running
    ///     commands such as <c>subscribe</c>.
    /// </summary>
    protected static void FlushLogging() => Log.CloseAndFlush();

    private bool _loggingConfigured;
}
