using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
using ZB.MOM.WW.Secrets.Abstractions;

namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer;

/// <summary>
/// Default factory that resolves protocol type strings to IDataConnection adapters.
/// Protocol extensibility: register new adapters via the constructor or AddAdapter method.
/// </summary>
public class DataConnectionFactory : IDataConnectionFactory
{
    private readonly Dictionary<string, Func<IDictionary<string, string>, IDataConnection>> _factories = new(StringComparer.OrdinalIgnoreCase);
    private readonly ILoggerFactory _loggerFactory;
    private readonly ISecretResolver? _secretResolver;

    /// <summary>
    /// Initializes a new <see cref="DataConnectionFactory"/> with default OPC UA global options.
    /// </summary>
    /// <param name="loggerFactory">Logger factory used when creating protocol adapters.</param>
    public DataConnectionFactory(ILoggerFactory loggerFactory)
        : this(loggerFactory, Options.Create(new OpcUaGlobalOptions())) { }

    /// <summary>
    /// Initializes a new <see cref="DataConnectionFactory"/> with explicit OPC UA global options.
    /// </summary>
    /// <param name="loggerFactory">Logger factory used when creating protocol adapters.</param>
    /// <param name="opcUaGlobalOptions">Global OPC UA options applied to all OPC UA connections created by this factory.</param>
    /// <param name="secretResolver">
    /// Optional secret resolver threaded into the MxGateway adapter so a per-endpoint
    /// <c>secret:&lt;name&gt;</c> ApiKey reference is resolved at connect time. DI supplies it on
    /// the Site container (where <see cref="ISecretResolver"/> is registered); direct
    /// construction without it keeps literal-key behaviour.
    /// </param>
    public DataConnectionFactory(
        ILoggerFactory loggerFactory,
        IOptions<OpcUaGlobalOptions> opcUaGlobalOptions,
        ISecretResolver? secretResolver = null,
        IOptions<DataConnectionOptions>? dataConnectionOptions = null)
    {
        _loggerFactory = loggerFactory;
        _secretResolver = secretResolver;
        var globalOptions = opcUaGlobalOptions.Value;
        // Only the MxGateway supervisory-advise window is adapter-level today; the rest of
        // DataConnectionOptions is consumed by the actor, which is constructed elsewhere.
        var supervisoryAdviseParallelism =
            (dataConnectionOptions?.Value ?? new DataConnectionOptions()).MxSupervisoryAdviseParallelism;

        // Register built-in protocols.
        // Pass the ILoggerFactory into RealOpcUaClientFactory so
        // the RealOpcUaClient it builds gets a real logger — without it the
        // auto-accept-certificate security warning is silently discarded by NullLogger.
        RegisterAdapter("OpcUa", details => new OpcUaDataConnection(
            new RealOpcUaClientFactory(globalOptions, _loggerFactory),
            _loggerFactory.CreateLogger<OpcUaDataConnection>()));

        // MxGateway: gRPC to the MxAccess Gateway. The RealMxGatewayClient wraps the
        // ZB.MOM.WW.MxGateway.Client package; per-connection config arrives via the
        // flat details dict (see MxGatewayEndpointConfigSerializer).
        RegisterAdapter("MxGateway", details => new MxGatewayDataConnection(
            new RealMxGatewayClientFactory(_loggerFactory),
            _loggerFactory.CreateLogger<MxGatewayDataConnection>(),
            _secretResolver,
            supervisoryAdviseParallelism));
    }

    /// <summary>
    /// Registers a new protocol adapter factory. This is the extension point
    /// for adding new protocols without modifying existing code.
    /// </summary>
    /// <param name="protocolType">The case-insensitive protocol type string (e.g. "OpcUa").</param>
    /// <param name="factory">Factory delegate that creates an <see cref="IDataConnection"/> from connection details.</param>
    public void RegisterAdapter(string protocolType, Func<IDictionary<string, string>, IDataConnection> factory)
    {
        _factories[protocolType] = factory;
    }

    /// <inheritdoc />
    public IDataConnection Create(string protocolType, IDictionary<string, string> connectionDetails)
    {
        if (!_factories.TryGetValue(protocolType, out var factory))
            throw new ArgumentException($"Unknown protocol type: {protocolType}. Registered protocols: {string.Join(", ", _factories.Keys)}");

        return factory(connectionDetails);
    }
}
