using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using Serilog;
using ZB.MOM.WW.OtOpcUa.Client.Shared;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Helpers;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;

namespace ZB.MOM.WW.OtOpcUa.Client.CLI;

/// <summary>
///     Abstract base class for all CLI commands providing common connection options and helpers.
/// </summary>
public abstract class CommandBase : ICommand
{
    internal static readonly IOpcUaClientServiceFactory DefaultFactory = new OpcUaClientServiceFactory();

    private readonly IOpcUaClientServiceFactory _factory;

    /// <summary>
    /// Initializes a CLI command with the shared OPC UA client factory used to create per-command sessions.
    /// </summary>
    /// <param name="factory">The factory that creates the shared client service for the command execution.</param>
    protected CommandBase(IOpcUaClientServiceFactory factory)
    {
        _factory = factory;
    }

    /// <summary>
    /// Gets the primary OPC UA endpoint URL the command should connect to.
    /// </summary>
    [CommandOption("url", 'u', Description = "OPC UA server endpoint URL", IsRequired = true)]
    public string Url { get; init; } = default!;

    /// <summary>
    /// Gets the optional username used for authenticated OPC UA sessions.
    /// </summary>
    [CommandOption("username", 'U', Description = "Username for authentication")]
    public string? Username { get; init; }

    /// <summary>
    /// Gets the optional password paired with <see cref="Username" /> for authenticated OPC UA sessions.
    /// </summary>
    [CommandOption("password", 'P', Description = "Password for authentication")]
    public string? Password { get; init; }

    /// <summary>
    /// Gets the transport security mode requested by the operator for this CLI session.
    /// </summary>
    [CommandOption("security", 'S',
        Description = "Transport security: none, sign, encrypt, signandencrypt (default: none)")]
    public string Security { get; init; } = "none";

    /// <summary>
    /// Gets the optional comma-separated failover endpoints that should be tried if the primary endpoint is unavailable.
    /// </summary>
    [CommandOption("failover-urls", 'F', Description = "Comma-separated failover endpoint URLs for redundancy")]
    public string? FailoverUrls { get; init; }

    /// <summary>
    /// Gets a value indicating whether verbose Serilog output should be enabled for troubleshooting.
    /// </summary>
    [CommandOption("verbose", Description = "Enable verbose/debug logging")]
    public bool Verbose { get; init; }

    /// <summary>
    /// Executes the command-specific workflow against the configured OPC UA endpoint.
    /// </summary>
    /// <param name="console">The CLI console used for output and cancellation handling.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public abstract ValueTask ExecuteAsync(IConsole console);

    /// <summary>
    ///     Creates a <see cref="ConnectionSettings" /> from the common command options.
    /// </summary>
    /// <returns>The connection settings built from the current command options.</returns>
    protected ConnectionSettings CreateConnectionSettings()
    {
        var securityMode = SecurityModeMapper.FromString(Security);
        var failoverUrls = !string.IsNullOrWhiteSpace(FailoverUrls)
            ? FailoverUrlParser.Parse(Url, FailoverUrls)
            : null;

        var settings = new ConnectionSettings
        {
            EndpointUrl = Url,
            FailoverUrls = failoverUrls,
            Username = Username,
            Password = Password,
            SecurityMode = securityMode,
            AutoAcceptCertificates = true
        };

        return settings;
    }

    /// <summary>
    ///     Creates a new <see cref="IOpcUaClientService" />, connects it using the common options,
    ///     and returns both the service and the connection info.
    /// </summary>
    /// <param name="ct">The cancellation token that aborts connection setup for the command.</param>
    /// <returns>The connected client service along with its connection info.</returns>
    protected async Task<(IOpcUaClientService Service, ConnectionInfo Info)> CreateServiceAndConnectAsync(
        CancellationToken ct)
    {
        var service = _factory.Create();
        var settings = CreateConnectionSettings();
        var info = await service.ConnectAsync(settings, ct);
        return (service, info);
    }

    /// <summary>
    ///     Configures Serilog based on the verbose flag.
    /// </summary>
    /// <remarks>
    ///     Disposes the previously assigned <see cref="Log.Logger" /> via <see cref="Log.CloseAndFlush" />
    ///     before installing the new one, so repeated CLI invocations (e.g. in the test suite) do not
    ///     leak the prior logger's console sink.
    /// </remarks>
    protected void ConfigureLogging()
    {
        // Dispose any previously installed logger before swapping in a new one.
        Log.CloseAndFlush();

        var config = new LoggerConfiguration();
        if (Verbose)
            config.MinimumLevel.Debug()
                .WriteTo.Console();
        else
            config.MinimumLevel.Warning()
                .WriteTo.Console();

        Log.Logger = config.CreateLogger();
    }
}
