using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;

namespace Tiku.Infrastructure.Security;

internal sealed class TenantFeatureCacheInvalidator(
    IMemoryCache memoryCache,
    IServiceProvider serviceProvider,
    ITenantRuntimeCacheInvalidator runtimeCacheInvalidator,
    ILogger<TenantFeatureCacheInvalidator> logger) : ITenantFeatureCacheInvalidator, IHostedService
{
    private const string ChannelName = "tiku:tenant-feature-snapshot:invalidate:v1";
    private ISubscriber? subscriber;

    public async Task InvalidateAsync(Guid tenantId, CancellationToken cancellationToken = default)
    {
        RemoveMemory(tenantId);
        await runtimeCacheInvalidator.InvalidateAsync(tenantId, cancellationToken);
        var distributedCache = serviceProvider.GetService<IDistributedCache>();
        if (distributedCache is not null)
        {
            try
            {
                await Task.WhenAll(
                    distributedCache.RemoveAsync(TenantFeatureSnapshotProvider.CacheKey(tenantId, FeatureAccessOperation.Read), cancellationToken),
                    distributedCache.RemoveAsync(TenantFeatureSnapshotProvider.CacheKey(tenantId, FeatureAccessOperation.Write), cancellationToken));
            }
            catch (Exception exception) when (exception is not OperationCanceledException)
            {
                logger.LogWarning(exception, "Tenant feature distributed cache invalidation failed for tenant {TenantId}.", tenantId);
            }
        }

        var connection = serviceProvider.GetService<IConnectionMultiplexer>();
        if (connection is not null)
        {
            try
            {
                await connection.GetSubscriber()
                    .PublishAsync(RedisChannel.Literal(ChannelName), tenantId.ToString("N"))
                    .WaitAsync(cancellationToken);
            }
            catch (Exception exception) when (exception is RedisException or TimeoutException)
            {
                logger.LogWarning(exception, "Tenant feature L1 invalidation broadcast failed for tenant {TenantId}.", tenantId);
            }
        }
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        var connection = serviceProvider.GetService<IConnectionMultiplexer>();
        if (connection is null)
        {
            return;
        }

        subscriber = connection.GetSubscriber();
        await subscriber.SubscribeAsync(RedisChannel.Literal(ChannelName), (_, value) =>
        {
            if (Guid.TryParseExact(value.ToString(), "N", out var tenantId))
            {
                RemoveMemory(tenantId);
            }
        }).WaitAsync(cancellationToken);
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        if (subscriber is not null)
        {
            await subscriber.UnsubscribeAsync(RedisChannel.Literal(ChannelName)).WaitAsync(cancellationToken);
        }
    }

    private void RemoveMemory(Guid tenantId)
    {
        memoryCache.Remove(TenantFeatureSnapshotProvider.CacheKey(tenantId, FeatureAccessOperation.Read));
        memoryCache.Remove(TenantFeatureSnapshotProvider.CacheKey(tenantId, FeatureAccessOperation.Write));
    }
}
