using Microsoft.Extensions.Options;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;

namespace Tiku.Api.Background;

internal sealed class DevelopmentTenantDomainLifecycleHostedService(
    IServiceScopeFactory scopeFactory,
    IOptions<DomainLifecycleOptions> domainOptions,
    ILogger<DevelopmentTenantDomainLifecycleHostedService> logger) : BackgroundService
{
    private readonly DomainLifecycleOptions options = domainOptions.Value;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        if (!options.EnableDevelopmentLocalhostBypass) return;

        var activeInterval = TimeSpan.FromSeconds(Math.Clamp(options.PollSeconds, 1, 3600));
        var maxIdleInterval = TimeSpan.FromSeconds(Math.Clamp(
            options.MaxIdlePollSeconds,
            (int)activeInterval.TotalSeconds,
            3600));
        var consecutiveIdleIterations = 0;
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await using var scope = scopeFactory.CreateAsyncScope();
                scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
                    .InitializeSystem(null, "Development .localhost domain lifecycle");
                var processed = await scope.ServiceProvider
                    .GetRequiredService<ITenantDomainLifecycleService>()
                    .ProcessPendingAsync(stoppingToken);
                consecutiveIdleIterations = processed == 0 ? consecutiveIdleIterations + 1 : 0;
                if (processed > 0)
                    logger.LogInformation("Processed {DomainCount} pending Development tenant domains.", processed);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception exception)
            {
                consecutiveIdleIterations++;
                logger.LogError(exception, "Development tenant domain lifecycle iteration failed.");
            }

            await Task.Delay(
                CalculateDelay(activeInterval, maxIdleInterval, consecutiveIdleIterations),
                stoppingToken);
        }
    }

    internal static TimeSpan CalculateDelay(
        TimeSpan activeInterval,
        TimeSpan maxIdleInterval,
        int consecutiveIdleIterations)
    {
        if (consecutiveIdleIterations <= 1) return activeInterval;

        var shift = Math.Min(consecutiveIdleIterations - 1, 20);
        var delayTicks = activeInterval.Ticks * (1L << shift);
        return TimeSpan.FromTicks(Math.Min(delayTicks, maxIdleInterval.Ticks));
    }
}
