using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ScadaLink.Communication.Grpc;

/// <summary>
/// Caches one <see cref="SiteStreamGrpcClient"/> per site identifier.
/// The DebugStreamBridgeActor uses this factory to obtain (or create) a
/// gRPC client for a given site before opening a streaming subscription.
/// </summary>
public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable
{
    private readonly ConcurrentDictionary<string, SiteStreamGrpcClient> _clients = new();
    private readonly ILoggerFactory _loggerFactory;
    private readonly CommunicationOptions _options;

    public SiteStreamGrpcClientFactory(ILoggerFactory loggerFactory)
        : this(loggerFactory, Options.Create(new CommunicationOptions()))
    {
    }

    /// <summary>
    /// DI constructor — flows <see cref="CommunicationOptions"/> into every created
    /// <see cref="SiteStreamGrpcClient"/> so the configured gRPC keepalive settings
    /// are applied rather than hard-coded defaults.
    /// </summary>
    public SiteStreamGrpcClientFactory(ILoggerFactory loggerFactory, IOptions<CommunicationOptions> options)
    {
        _loggerFactory = loggerFactory;
        _options = options.Value;
    }

    /// <summary>
    /// Returns an existing client for the site or creates a new one. The new
    /// client is created via <see cref="CreateClient"/> and tracked so the
    /// factory's <see cref="Dispose"/> / <see cref="DisposeAsync"/> release it.
    /// </summary>
    public virtual SiteStreamGrpcClient GetOrCreate(string siteIdentifier, string grpcEndpoint)
    {
        return _clients.GetOrAdd(siteIdentifier, _ => CreateClient(grpcEndpoint));
    }

    /// <summary>
    /// Creates a single <see cref="SiteStreamGrpcClient"/>. Overridable so tests
    /// can substitute a tracking client while still exercising the factory's real
    /// caching and disposal machinery.
    /// </summary>
    protected virtual SiteStreamGrpcClient CreateClient(string grpcEndpoint)
    {
        var logger = _loggerFactory.CreateLogger<SiteStreamGrpcClient>();
        return new SiteStreamGrpcClient(grpcEndpoint, logger, _options);
    }

    /// <summary>
    /// Removes and disposes the client for the given site.
    /// </summary>
    public async Task RemoveSiteAsync(string siteIdentifier)
    {
        if (_clients.TryRemove(siteIdentifier, out var client))
        {
            await client.DisposeAsync();
        }
    }

    public async ValueTask DisposeAsync()
    {
        foreach (var client in _clients.Values)
        {
            await client.DisposeAsync();
        }
        _clients.Clear();
    }

    /// <summary>
    /// Synchronous disposal. Communication-007: this used to block on
    /// <c>DisposeAsync().AsTask().GetAwaiter().GetResult()</c> (sync-over-async,
    /// a stall/deadlock risk during host shutdown). Each
    /// <see cref="SiteStreamGrpcClient"/> releases all of its resources
    /// synchronously, so we dispose them directly with no async path.
    /// </summary>
    public void Dispose()
    {
        foreach (var client in _clients.Values)
        {
            client.Dispose();
        }
        _clients.Clear();
    }
}
