using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Core.Hosting;

/// <summary>
///     Process-singleton registry of <see cref="IDriver"/> factories keyed by
///     <c>DriverInstance.DriverType</c> string. Each driver project ships a DI
///     extension (e.g. <c>services.AddGalaxyProxyDriverFactory()</c>) that registers
///     its factory at startup; the bootstrapper looks up the factory by
///     <c>DriverInstance.DriverType</c> + invokes it with the row's
///     <c>DriverInstanceId</c> + <c>DriverConfig</c> JSON.
/// </summary>
/// <remarks>
///     Closes a gap where DriverInstance rows in
///     the central config DB had no path to materialise as registered <see cref="IDriver"/>
///     instances. The factory registry is the seam.
/// </remarks>
public sealed class DriverFactoryRegistry
{
    private readonly Dictionary<string, Func<string, string, IDriver>> _factories
        = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, DriverTier> _tiers
        = new(StringComparer.OrdinalIgnoreCase);
    private readonly object _lock = new();

    /// <summary>
    ///     Register a factory for <paramref name="driverType"/>. Throws if a factory is
    ///     already registered for that type — drivers are singletons by type-name in
    ///     this process.
    /// </summary>
    /// <param name="driverType">Matches <c>DriverInstance.DriverType</c>.</param>
    /// <param name="factory">
    ///     Receives <c>(driverInstanceId, driverConfigJson)</c>; returns a new
    ///     <see cref="IDriver"/>. Must NOT call <see cref="IDriver.InitializeAsync"/>
    ///     itself — the bootstrapper calls it via <see cref="DriverHost.RegisterAsync"/>
    ///     so the host's per-driver retry semantics apply uniformly.
    /// </param>
    /// <param name="tier">
    ///     Stability tier per <c>docs/v2/driver-stability.md</c>. Defaults to
    ///     <see cref="DriverTier.A"/> (in-process, lowest recycle footprint); Tier C
    ///     drivers SHOULD pass this explicitly so the scheduled-recycle wiring in
    ///     <c>DriverInstanceBootstrapper</c> can skip in-process drivers by tier check
    ///     rather than by driver-type allow-list.
    /// </param>
    public void Register(string driverType, Func<string, string, IDriver> factory,
        DriverTier tier = DriverTier.A)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
        ArgumentNullException.ThrowIfNull(factory);
        lock (_lock)
        {
            if (_factories.ContainsKey(driverType))
                throw new InvalidOperationException(
                    $"DriverType '{driverType}' factory already registered for this process");
            _factories[driverType] = factory;
            _tiers[driverType] = tier;
        }
    }

    /// <summary>
    ///     Try to look up the factory for <paramref name="driverType"/>. Returns null
    ///     if no driver assembly registered one — bootstrapper logs + skips so a
    ///     missing-assembly deployment doesn't take down the whole server.
    /// </summary>
    /// <param name="driverType">The driver type to look up.</param>
    /// <returns>The registered factory, or <see langword="null"/> if none is registered for <paramref name="driverType"/>.</returns>
    public Func<string, string, IDriver>? TryGet(string driverType)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
        lock (_lock) return _factories.GetValueOrDefault(driverType);
    }

    /// <summary>
    ///     Look up the tier recorded alongside the factory. Returns <see cref="DriverTier.A"/>
    ///     for unknown driver types — a missing registration is already a skipped-bootstrap
    ///     case upstream; we don't double-surface that failure here.
    /// </summary>
    /// <param name="driverType">The driver type to look up.</param>
    /// <returns>The recorded stability tier, or <see cref="DriverTier.A"/> if unknown.</returns>
    public DriverTier GetTier(string driverType)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
        lock (_lock) return _tiers.GetValueOrDefault(driverType, DriverTier.A);
    }

    /// <summary>Gets the collection of registered driver type names.</summary>
    public IReadOnlyCollection<string> RegisteredTypes
    {
        get { lock (_lock) return [.. _factories.Keys]; }
    }
}
