using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Jobs;
using Tiku.Application.Security;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class CapabilityAuthorizationTests
{
    [Fact]
    public async Task Background_job_rechecks_feature_access_after_enqueue_before_execution()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        var fixture = CreateSubscriptionFixture(tenantId, SaasFeatureCatalog.PrivateQuestionBank, true);
        await factory.SeedAsync(fixture.Entities);

        using var scope = factory.CreateSystemScope("Verify job feature access at consumption");
        var jobs = scope.ServiceProvider.GetRequiredService<IBackgroundJobService>();
        var job = await jobs.EnqueueAsync(new CreateBackgroundJobCommand(
            tenantId,
            "content_export",
            JsonSerializer.SerializeToElement(new { exportType = "feature-test" })));

        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        dbContext.TenantFeatureOverrides.Add(new TenantFeatureOverride
        {
            TenantId = tenantId,
            FeatureCode = SaasFeatureCatalog.PrivateQuestionBank,
            Mode = TenantFeatureOverrideMode.Disabled,
            Reason = "Integration test revocation"
        });
        await dbContext.SaveChangesAsync();

        Assert.True(await jobs.ProcessRequestedAsync(
            job.Id, tenantId, job.JobType, "feature-test-worker"));
        var stored = await dbContext.BackgroundJobs.AsNoTracking().SingleAsync(item => item.Id == job.Id);
        Assert.Equal(BackgroundJobStatus.Failed, stored.Status);
        Assert.Equal("Tenant feature entitlement was revoked before job execution.", stored.LastError);
    }

    [Fact]
    public async Task Feature_access_is_database_backed_supports_overrides_and_makes_past_due_read_only()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        var fixture = CreateSubscriptionFixture(tenantId, SaasFeatureCatalog.PrivateQuestionBank, true);
        await factory.SeedAsync(fixture.Entities);

        await factory.SeedAsync(new SaasFeature
        {
            Code = SaasFeatureCatalog.Video,
            Name = "Video",
            Category = "content",
            Status = SaasFeatureStatus.Active
        });

        using var scope = factory.CreateSystemScope("Verify feature authorization");
        var access = scope.ServiceProvider.GetRequiredService<IFeatureAccessService>();
        var missing = await access.EvaluateAsync(
            tenantId,
            SaasFeatureCatalog.Video,
            FeatureAccessOperation.Read);
        Assert.False(missing.Allowed);
        Assert.Equal("feature_not_purchased", missing.DenialCode);

        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.True((await access.EvaluateAsync(
            tenantId,
            SaasFeatureCatalog.PrivateQuestionBank,
            FeatureAccessOperation.Write)).Allowed);

        var featureOverride = new TenantFeatureOverride
        {
            TenantId = tenantId,
            FeatureCode = SaasFeatureCatalog.PrivateQuestionBank,
            Mode = TenantFeatureOverrideMode.Disabled,
            Reason = "Integration test disable"
        };
        dbContext.TenantFeatureOverrides.Add(featureOverride);
        await dbContext.SaveChangesAsync();
        var disabled = await access.EvaluateAsync(
            tenantId,
            SaasFeatureCatalog.PrivateQuestionBank,
            FeatureAccessOperation.Read);
        Assert.False(disabled.Allowed);
        Assert.Equal("feature_disabled", disabled.DenialCode);

        featureOverride.Mode = TenantFeatureOverrideMode.Enabled;
        await dbContext.SaveChangesAsync();
        Assert.True((await access.EvaluateAsync(
            tenantId,
            SaasFeatureCatalog.PrivateQuestionBank,
            FeatureAccessOperation.Write)).Allowed);

        var subscription =
            await dbContext.TenantSaasSubscriptions.SingleAsync(item => item.Id == fixture.SubscriptionId);
        subscription.Status = TenantSaasSubscriptionStatus.PastDue;
        await dbContext.SaveChangesAsync();
        Assert.True((await access.EvaluateAsync(
            tenantId,
            SaasFeatureCatalog.PrivateQuestionBank,
            FeatureAccessOperation.Read)).Allowed);
        var pastDueWrite = await access.EvaluateAsync(
            tenantId,
            SaasFeatureCatalog.PrivateQuestionBank,
            FeatureAccessOperation.Write);
        Assert.False(pastDueWrite.Allowed);
        Assert.Equal("subscription_read_only", pastDueWrite.DenialCode);
    }

    private static SubscriptionFixture CreateSubscriptionFixture(
        Guid tenantId,
        string featureCode,
        bool includeEntitlement)
    {
        var offeringId = Guid.NewGuid();
        var versionId = Guid.NewGuid();
        var subscriptionId = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;
        var entities = new List<object>
        {
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Feature Access Tenant"
            },
            new SaasFeature
            {
                Code = featureCode,
                Name = "Private Question Bank",
                Category = "content",
                Status = SaasFeatureStatus.Active
            },
            new SaasOffering
            {
                Id = offeringId,
                Code = $"feature-test-{tenantId:N}",
                Name = "Feature Access Test",
                Type = SaasOfferingType.BasePlan,
                Status = SaasOfferingStatus.Active
            },
            new SaasOfferingVersion
            {
                Id = versionId,
                OfferingId = offeringId,
                Version = 1,
                Status = SaasOfferingVersionStatus.Published,
                PublishedAt = now.AddDays(-1),
                EffectiveAt = now.AddDays(-1)
            },
            new TenantSaasSubscription
            {
                Id = subscriptionId,
                TenantId = tenantId,
                BaseOfferingVersionId = versionId,
                Status = TenantSaasSubscriptionStatus.Active,
                StartsAt = now.AddDays(-1),
                CurrentPeriodStart = now.AddDays(-1),
                CurrentPeriodEnd = now.AddDays(30)
            },
            new TenantSaasSubscriptionItem
            {
                TenantId = tenantId,
                SubscriptionId = subscriptionId,
                OfferingVersionId = versionId,
                ItemType = TenantSaasSubscriptionItemType.BasePlan,
                Status = TenantSaasSubscriptionItemStatus.Active,
                StartsAt = now.AddDays(-1),
                EndsAt = now.AddDays(30)
            }
        };
        if (includeEntitlement)
            entities.Add(new SaasOfferingVersionFeature
            {
                OfferingVersionId = versionId,
                FeatureCode = featureCode
            });

        return new SubscriptionFixture(versionId, subscriptionId, entities.ToArray());
    }

    private sealed record SubscriptionFixture(
        Guid VersionId,
        Guid SubscriptionId,
        object[] Entities);
}