using Microsoft.EntityFrameworkCore;
using Tiku.Application.Jobs;
using Tiku.Application.Security;
using Tiku.Domain.Operations;

namespace Tiku.Infrastructure.Jobs;

internal sealed partial class BackgroundJobService
{
    public async Task<BackgroundJobItem> EnqueueAsync(
        CreateBackgroundJobCommand command,
        CancellationToken cancellationToken = default)
    {
        var normalizedJobType = NormalizeJobType(command.JobType);
        var idempotencyKey = NormalizeIdempotencyKey(command.IdempotencyKey);
        if (idempotencyKey is not null)
        {
            var existing = await dbContext.BackgroundJobs.AsNoTracking().SingleOrDefaultAsync(
                item => item.TenantId == command.TenantId && item.JobType == normalizedJobType &&
                        item.IdempotencyKey == idempotencyKey,
                cancellationToken);
            if (existing is not null) return ToItem(existing);
        }

        if (!command.IsSystemJob && !(await featureAccessService.EvaluateAsync(
                command.TenantId,
                ResolveRequiredFeature(normalizedJobType, command.Payload),
                FeatureAccessOperation.Write,
                cancellationToken)).Allowed)
            throw new InvalidOperationException("Tenant feature entitlement does not allow this background job.");
        var quotaMetric = command.IsSystemJob ? null : ResolveQuotaMetric(normalizedJobType);
        var quotaConsumed = false;
        if (quotaMetric is not null)
        {
            var quota = (await featureAccessService.GetQuotaSummaryAsync(command.TenantId, cancellationToken))
                .SingleOrDefault(value => value.MetricCode == quotaMetric);
            if (quota is not null)
            {
                quotaConsumed = await featureAccessService.TryConsumeQuotaAsync(
                    command.TenantId, quotaMetric, 1, cancellationToken);
                if (!quotaConsumed)
                    throw new FeatureAccessException("The background job quota has been exhausted.",
                        "feature_quota_exhausted");
            }
        }

        var job = new BackgroundJob
        {
            TenantId = command.TenantId,
            JobType = normalizedJobType,
            IdempotencyKey = idempotencyKey,
            Payload = command.Payload,
            RunAfter = command.RunAfter,
            MaxRetries = Math.Clamp(command.MaxRetries, 0, 20)
        };
        dbContext.BackgroundJobs.Add(job);
        try
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateException) when (idempotencyKey is not null)
        {
            dbContext.ChangeTracker.Clear();
            var existing = await dbContext.BackgroundJobs.AsNoTracking().SingleOrDefaultAsync(
                item => item.TenantId == command.TenantId && item.JobType == normalizedJobType &&
                        item.IdempotencyKey == idempotencyKey,
                cancellationToken);
            if (existing is not null)
            {
                if (quotaConsumed && quotaMetric is not null)
                    await featureAccessService.ReleaseQuotaAsync(command.TenantId, quotaMetric, 1,
                        CancellationToken.None);
                return ToItem(existing);
            }

            throw;
        }
        catch
        {
            if (quotaConsumed && quotaMetric is not null)
                await featureAccessService.ReleaseQuotaAsync(command.TenantId, quotaMetric, 1, CancellationToken.None);
            throw;
        }

        return ToItem(job);
    }

    private static string? ResolveQuotaMetric(string jobType)
    {
        return jobType switch
        {
            "content_import" => SaasQuotaMetricCatalog.ImportCount,
            "content_export" => SaasQuotaMetricCatalog.ExportCount,
            _ => null
        };
    }
}