using System.Collections.Concurrent;

namespace EmpyrionBridge;

// Lazy connection pool keyed by host:port. Reuses an EPM TCP connection
// across HTTP requests; drops it after IdleTtl with no traffic.
public class BridgePool : IAsyncDisposable
{
    public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(8);
    public TimeSpan IdleTtl { get; init; } = TimeSpan.FromMinutes(5);

    readonly ConcurrentDictionary<string, Entry> _entries = new();
    readonly ILoggerFactory _logFactory;
    readonly ILogger<BridgePool> _log;
    readonly Timer _reaper;

    public BridgePool(ILoggerFactory logFactory)
    {
        _logFactory = logFactory;
        _log = logFactory.CreateLogger<BridgePool>();
        _reaper = new Timer(Reap, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
    }

    public async Task<BridgeConnection> GetAsync(string host, int port)
    {
        var key = $"{host}:{port}";
        Entry e;
        lock (_entries)
        {
            if (!_entries.TryGetValue(key, out e))
            {
                e = new Entry { Conn = new BridgeConnection(host, port, _logFactory.CreateLogger("EPM")) };
                _entries[key] = e;
            }
            e.LastUsed = DateTime.UtcNow;
        }

        if (!e.Conn.IsConnected)
        {
            await e.ConnectGate.WaitAsync();
            try
            {
                if (!e.Conn.IsConnected)
                {
                    await e.Conn.ConnectAsync(ConnectTimeout);
                }
            }
            finally
            {
                e.ConnectGate.Release();
            }
        }
        return e.Conn;
    }

    void Reap(object _)
    {
        var cutoff = DateTime.UtcNow - IdleTtl;
        foreach (var kv in _entries.ToArray())
        {
            if (kv.Value.LastUsed < cutoff)
            {
                if (_entries.TryRemove(kv.Key, out var e))
                {
                    _log.LogInformation("reaping idle connection {Key}", kv.Key);
                    e.Conn.Dispose();
                }
            }
        }
    }

    public async ValueTask DisposeAsync()
    {
        await _reaper.DisposeAsync();
        foreach (var e in _entries.Values) e.Conn.Dispose();
        _entries.Clear();
    }

    class Entry
    {
        public BridgeConnection Conn = null!;
        public DateTime LastUsed = DateTime.UtcNow;
        public SemaphoreSlim ConnectGate = new(1, 1);
    }
}
