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

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

/// <summary>
///     Process-local registry + lifecycle manager for loaded <see cref="IDriver"/> instances.
///     Phase 1 scaffold — per-process isolation for Tier C drivers (Galaxy, FOCAS)
///     is implemented in Phase 2 via named-pipe RPC; this class handles in-process drivers today
///     and exposes the same registration interface so the Tier C wrapper can slot in later.
/// </summary>
public sealed class DriverHost : IAsyncDisposable
{
    private readonly Dictionary<string, IDriver> _drivers = new();
    private readonly object _lock = new();

    /// <summary>Gets the collection of registered driver instance identifiers.</summary>
    public IReadOnlyCollection<string> RegisteredDriverIds
    {
        get { lock (_lock) return [.. _drivers.Keys]; }
    }

    /// <summary>Gets the health status of a registered driver.</summary>
    /// <param name="driverInstanceId">The driver instance identifier to query.</param>
    /// <returns>The driver's health status, or <c>null</c> when not registered.</returns>
    public DriverHealth? GetHealth(string driverInstanceId)
    {
        lock (_lock)
            return _drivers.TryGetValue(driverInstanceId, out var d) ? d.GetHealth() : null;
    }

    /// <summary>
    ///     Look up a registered driver by instance id. Used by the OPC UA server runtime
    ///     (<c>OtOpcUaServer</c>) to instantiate one <c>DriverNodeManager</c> per driver at
    ///     startup. Returns null when the driver is not registered.
    /// </summary>
    /// <param name="driverInstanceId">The driver instance identifier to look up.</param>
    /// <returns>The registered driver, or <c>null</c> when not registered.</returns>
    public IDriver? GetDriver(string driverInstanceId)
    {
        lock (_lock)
            return _drivers.TryGetValue(driverInstanceId, out var d) ? d : null;
    }

    /// <summary>
    ///     Registers the driver and calls <see cref="IDriver.InitializeAsync"/>. If initialization
    ///     throws, the driver is kept in the registry so the operator can retry; quality on its
    ///     nodes will reflect <see cref="DriverState.Faulted"/> until <c>Reinitialize</c> succeeds.
    /// </summary>
    /// <param name="driver">The driver instance to register.</param>
    /// <param name="driverConfigJson">The configuration JSON for the driver.</param>
    /// <param name="ct">Cancellation token for the operation.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public async Task RegisterAsync(IDriver driver, string driverConfigJson, CancellationToken ct)
    {
        ArgumentNullException.ThrowIfNull(driver);
        var id = driver.DriverInstanceId;

        lock (_lock)
        {
            if (_drivers.ContainsKey(id))
                throw new InvalidOperationException($"Driver '{id}' is already registered.");
            _drivers[id] = driver;
        }

        try { await driver.InitializeAsync(driverConfigJson, ct).ConfigureAwait(false); }
        catch
        {
            // Keep the driver registered — operator will see Faulted state and can reinitialize.
            throw;
        }
    }

    /// <summary>Unregisters a driver and calls shutdown.</summary>
    /// <param name="driverInstanceId">The driver instance identifier to unregister.</param>
    /// <param name="ct">Cancellation token for the operation.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public async Task UnregisterAsync(string driverInstanceId, CancellationToken ct)
    {
        IDriver? driver;
        lock (_lock)
        {
            if (!_drivers.TryGetValue(driverInstanceId, out driver)) return;
            _drivers.Remove(driverInstanceId);
        }

        try { await driver.ShutdownAsync(ct).ConfigureAwait(false); }
        catch { /* shutdown is best-effort; logs elsewhere */ }
    }

    /// <summary>Disposes the driver host and all registered drivers.</summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public async ValueTask DisposeAsync()
    {
        List<IDriver> snapshot;
        lock (_lock)
        {
            snapshot = [.. _drivers.Values];
            _drivers.Clear();
        }

        foreach (var driver in snapshot)
        {
            try { await driver.ShutdownAsync(CancellationToken.None).ConfigureAwait(false); } catch { /* ignore */ }
            (driver as IDisposable)?.Dispose();
        }
    }
}
