using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using ServiceStack.Logging;

namespace ServiceStack.Redis;

/// <summary>
/// Don't immediately kill connections of active clients after failover to give them a chance to dispose gracefully. 
/// Deactivating clients are automatically cleared from the pool.
/// </summary>
internal static class RedisState
{
    private static ILog log = LogManager.GetLogger(typeof(RedisState));

    internal static long TotalCommandsSent = 0;
    internal static long TotalFailovers = 0;
    internal static long TotalDeactivatedClients = 0;
    internal static long TotalFailedSentinelWorkers = 0;
    internal static long TotalForcedMasterFailovers = 0;
    internal static long TotalInvalidMasters = 0;
    internal static long TotalNoMastersFound = 0;
    internal static long TotalClientsCreated = 0;
    internal static long TotalClientsCreatedOutsidePool = 0;
    internal static long TotalSubjectiveServersDown = 0;
    internal static long TotalObjectiveServersDown = 0;
    internal static long TotalRetryCount = 0;
    internal static long TotalRetrySuccess = 0;
    internal static long TotalRetryTimedout = 0;

    internal static readonly ConcurrentDictionary<RedisClient, DateTime> DeactivatedClients = new();

    internal static void DeactivateClient(RedisClient client)
    {
        Interlocked.Increment(ref TotalDeactivatedClients);

        if (RedisConfig.DeactivatedClientsExpiry == TimeSpan.Zero)
        {
            client.DisposeConnection();
            return;
        }

        var deactivatedAt = client.DeactivatedAt ?? DateTime.UtcNow;
        client.DeactivatedAt = deactivatedAt;

        if (!DeactivatedClients.TryAdd(client, deactivatedAt))
            client.DisposeConnection();
    }

    internal static void DisposeExpiredClients()
    {
        if (RedisConfig.DeactivatedClientsExpiry == TimeSpan.Zero || DeactivatedClients.Count == 0)
            return;

        var now = DateTime.UtcNow;
        var removeDisposed = new List<RedisClient>();

        foreach (var entry in DeactivatedClients)
        {
            try
            {
                if (now - entry.Value <= RedisConfig.DeactivatedClientsExpiry)
                    continue;

                if (log.IsDebugEnabled)
                    log.Debug("Disposed Deactivated Client: {0}".Fmt(entry.Key.GetHostString()));

                entry.Key.DisposeConnection();
                removeDisposed.Add(entry.Key);
            }
            catch
            {
                removeDisposed.Add(entry.Key);
            }
        }

        if (removeDisposed.Count == 0)
            return;

        var dict = ((IDictionary<RedisClient, DateTime>)DeactivatedClients);
        foreach (var client in removeDisposed)
        {
            dict.Remove(client);
        }
    }

    internal static void DisposeAllDeactivatedClients()
    {
        if (RedisConfig.DeactivatedClientsExpiry == TimeSpan.Zero)
            return;

        var allClients = DeactivatedClients.Keys.ToArray();
        DeactivatedClients.Clear();
        foreach (var client in allClients)
        {
            if (log.IsDebugEnabled)
                log.Debug("Disposed Deactivated Client (All): {0}".Fmt(client.GetHostString()));

            client.DisposeConnection();
        }
    }
}