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

namespace Tiku.Api.BackgroundProcessing;

public sealed class BackgroundProcessingOptions
{
    public const string SectionName = "BackgroundProcessing";

    public bool Enabled { get; set; } = true;
    public int JobPollSeconds { get; set; } = 2;
    public int JobParallelism { get; set; } = 4;
    public int JobBatchSize { get; set; } = 5;

    public static bool BeValid(BackgroundProcessingOptions options) =>
        options.JobPollSeconds is >= 1 and <= 3600 &&
        options.JobParallelism is >= 1 and <= 32 &&
        options.JobBatchSize is >= 1 and <= 100;
}

internal abstract class PeriodicBackgroundService(
    ILogger logger,
    TimeSpan interval,
    bool enabled) : BackgroundService
{
    protected abstract Task<int> ProcessAsync(CancellationToken cancellationToken);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        if (!enabled)
        {
            return;
        }

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var processed = await ProcessAsync(stoppingToken);
                if (processed > 0)
                {
                    logger.LogInformation("{Worker} processed {Count} items.", GetType().Name, processed);
                }

                await Task.Delay(interval, stoppingToken);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception exception)
            {
                logger.LogError(exception, "{Worker} iteration failed.", GetType().Name);
                try
                {
                    await Task.Delay(interval, stoppingToken);
                }
                catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
                {
                    break;
                }
            }
        }
    }

    protected static void InitializeSystem(IServiceProvider services, string reason) =>
        services.GetRequiredService<ITenantContextInitializer>().InitializeSystem(null, reason);
}

internal sealed class TenantDomainBackgroundService(
    IServiceScopeFactory scopeFactory,
    IOptions<DomainLifecycleOptions> domainOptions,
    IOptions<BackgroundProcessingOptions> backgroundOptions,
    ILogger<TenantDomainBackgroundService> logger)
    : PeriodicBackgroundService(
        logger,
        TimeSpan.FromSeconds(Math.Clamp(domainOptions.Value.PollSeconds, 10, 3600)),
        backgroundOptions.Value.Enabled)
{
    protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
    {
        await using var scope = scopeFactory.CreateAsyncScope();
        InitializeSystem(scope.ServiceProvider, "Tenant domain DNS and TLS lifecycle background service");
        return await scope.ServiceProvider.GetRequiredService<ITenantDomainLifecycleService>()
            .ProcessPendingAsync(cancellationToken);
    }
}

internal sealed class SaasSubscriptionBackgroundService(
    IServiceScopeFactory scopeFactory,
    IOptions<BackgroundProcessingOptions> options,
    ILogger<SaasSubscriptionBackgroundService> logger)
    : PeriodicBackgroundService(logger, TimeSpan.FromSeconds(60), options.Value.Enabled)
{
    protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
    {
        await using var scope = scopeFactory.CreateAsyncScope();
        InitializeSystem(scope.ServiceProvider, "SaaS subscription lifecycle background service");
        return await scope.ServiceProvider.GetRequiredService<ISaasSubscriptionLifecycleService>()
            .ProcessDueAsync(cancellationToken: cancellationToken);
    }
}

internal sealed class FeatureUsageBackgroundService(
    IServiceScopeFactory scopeFactory,
    IOptions<FeatureUsageReconciliationOptions> featureOptions,
    IOptions<BackgroundProcessingOptions> backgroundOptions,
    ILogger<FeatureUsageBackgroundService> logger)
    : PeriodicBackgroundService(
        logger,
        TimeSpan.FromMinutes(Math.Clamp(featureOptions.Value.IntervalMinutes, 1, 1440)),
        backgroundOptions.Value.Enabled)
{
    protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
    {
        await using var scope = scopeFactory.CreateAsyncScope();
        InitializeSystem(scope.ServiceProvider, "Tenant feature usage reconciliation background service");
        return await scope.ServiceProvider.GetRequiredService<IFeatureUsageReconciliationService>()
            .ProcessDueAsync(cancellationToken);
    }
}

internal sealed class BackgroundJobsBackgroundService(
    IServiceScopeFactory scopeFactory,
    IOptions<BackgroundProcessingOptions> options,
    ILogger<BackgroundJobsBackgroundService> logger)
    : PeriodicBackgroundService(logger, TimeSpan.FromSeconds(options.Value.JobPollSeconds), options.Value.Enabled)
{
    private readonly string workerId = $"{Environment.MachineName}:{Guid.NewGuid():N}";
    private readonly int parallelism = options.Value.JobParallelism;
    private readonly int batchSize = options.Value.JobBatchSize;

    protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
    {
        var workers = Enumerable.Range(0, parallelism)
            .Select(index => ProcessPartitionAsync(index, cancellationToken));
        return (await Task.WhenAll(workers)).Sum();
    }

    private async Task<int> ProcessPartitionAsync(int index, CancellationToken cancellationToken)
    {
        await using var scope = scopeFactory.CreateAsyncScope();
        InitializeSystem(scope.ServiceProvider, "Background job lease service");
        return await scope.ServiceProvider.GetRequiredService<IBackgroundJobService>()
            .ProcessPendingAsync(
                $"{workerId}:{index}",
                batchSize,
                includeImmediateJobs: true,
                cancellationToken: cancellationToken);
    }
}
