using System.Collections.Concurrent;
using GestionaDenuncias.Shared.Models;

namespace ApiDenuncias.Services;

public sealed class GlobalLeaksSessionKeepAliveService
{
    private static readonly TimeSpan MinimumRefreshInterval = TimeSpan.FromSeconds(20);
    private static readonly ConcurrentDictionary<string, SemaphoreSlim> UserGates =
        new(StringComparer.OrdinalIgnoreCase);

    private readonly GlobalLeaksSessionStore _sessionStore;
    private readonly GlobalLeaksClient _globalLeaksClient;
    private readonly ILogger<GlobalLeaksSessionKeepAliveService> _logger;

    public GlobalLeaksSessionKeepAliveService(
        GlobalLeaksSessionStore sessionStore,
        GlobalLeaksClient globalLeaksClient,
        ILogger<GlobalLeaksSessionKeepAliveService> logger)
    {
        _sessionStore = sessionStore;
        _globalLeaksClient = globalLeaksClient;
        _logger = logger;
    }

    public async Task<GlobalLeaksStoredSession?> KeepAliveAsync(
        string username,
        CancellationToken cancellationToken = default)
    {
        var gate = UserGates.GetOrAdd(username, static _ => new SemaphoreSlim(1, 1));
        await gate.WaitAsync(cancellationToken);
        try
        {
            var session = await _sessionStore.GetAsync(username, cancellationToken);
            if (session?.HasActiveSession != true)
            {
                return session;
            }

            if (session.LastKeepAliveAtUtc is { } lastKeepAlive &&
                DateTimeOffset.UtcNow - lastKeepAlive < MinimumRefreshInterval)
            {
                return session;
            }

            var expectedSessionId = session.SessionId!;
            try
            {
                var refreshed = await _globalLeaksClient.RefreshSessionAsync(
                    expectedSessionId,
                    session.DpopPrivateKey!,
                    session.ProofOfWorkToken!,
                    cancellationToken);

                var updated = await _sessionStore.UpdateKeepAliveAsync(
                    username,
                    expectedSessionId,
                    refreshed.ProofOfWorkToken,
                    refreshed.SessionExpiresAtUtc,
                    cancellationToken);

                if (updated)
                {
                    _logger.LogDebug("Sesion GlobalLeaks renovada para {Username}.", username);
                }

                return await _sessionStore.GetAsync(username, cancellationToken);
            }
            catch (GlobalLeaksSessionExpiredException)
            {
                await _sessionStore.ClearSessionIfMatchesAsync(
                    username,
                    expectedSessionId,
                    cancellationToken);
                throw;
            }
        }
        finally
        {
            gate.Release();
        }
    }
}
