using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Contracts;
using Tiku.Application.Security;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Identity;
using Tiku.Domain.Platform;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class CurrentQuotaEnforcementTests
{
    [Fact]
    public async Task Reconciliation_uses_audited_tenant_scope_and_idempotently_records_actual_values_above_limits()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLimitedTenantAsync(factory);
        var teacherId = Guid.NewGuid();
        var studentA = Guid.NewGuid();
        var studentB = Guid.NewGuid();
        await factory.SeedAsync(
            new User { Id = teacherId, Phone = "13940000001", Name = "Reconcile Teacher" },
            new User { Id = studentA, Phone = "13940000002", Name = "Reconcile Student A" },
            new User { Id = studentB, Phone = "13940000003", Name = "Reconcile Student B" },
            new TenantMembership { TenantId = seed.TenantId, UserId = teacherId, Role = TenantRole.Teacher, Status = MembershipStatus.Active },
            new TenantMembership { TenantId = seed.TenantId, UserId = studentA, Role = TenantRole.Student, Status = MembershipStatus.Active },
            new TenantMembership { TenantId = seed.TenantId, UserId = studentB, Role = TenantRole.Student, Status = MembershipStatus.Active },
            new Question { TenantId = seed.TenantId, Type = "choice", Status = QuestionStatus.Published },
            new Question { TenantId = seed.TenantId, Type = "choice", Status = QuestionStatus.Draft },
            new ContentAsset
            {
                TenantId = seed.TenantId,
                AssetKey = $"reconcile-{Guid.NewGuid():N}",
                Status = ContentStatus.Active,
                VerifiedSizeBytes = 2
            });

        using var scope = factory.CreateSystemScope("Run feature usage reconciliation test");
        var service = scope.ServiceProvider.GetRequiredService<IFeatureUsageReconciliationService>();
        var request = new ReconcileFeatureUsageRequest(
            seed.TenantId,
            SystemScopeCallerType.Test,
            nameof(CurrentQuotaEnforcementTests),
            "Verify current feature usage reconciliation",
            $"quota-reconcile-{Guid.NewGuid():N}");
        var first = await service.ReconcileTenantAsync(request);
        var firstVersions = await ReadUsageVersionsAsync(factory, seed.TenantId);
        var second = await service.ReconcileTenantAsync(request with { CorrelationId = $"{request.CorrelationId}-again" });
        var secondVersions = await ReadUsageVersionsAsync(factory, seed.TenantId);

        Assert.Equal(4, first.Count);
        Assert.All(first, item => Assert.True(item.Exceeded));
        Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.StaffCount).ActualValue);
        Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.StudentCount).ActualValue);
        Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.PrivateQuestionCount).ActualValue);
        Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.StorageBytes).ActualValue);
        Assert.Equal(first, second);
        Assert.Equal(firstVersions.Count, secondVersions.Count);
        Assert.All(firstVersions, item => Assert.Equal(item.Value, secondVersions[item.Key]));
        using var verificationScope = factory.CreateSystemScope("Verify reconciliation audit");
        Assert.True(await verificationScope.ServiceProvider.GetRequiredService<TikuDbContext>().AuditLogs.AnyAsync(item =>
            item.TenantId == seed.TenantId &&
            item.Action == "system_scope.completed" &&
            item.TargetId == request.CorrelationId));
    }

    [Fact]
    public async Task Private_question_quota_is_atomic_and_archiving_releases_capacity()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLimitedTenantAsync(factory);
        using var client = factory.CreateClient();
        client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));

        Task<HttpResponseMessage> CreateAsync(string content) => client.PostAsJsonAsync(
            "/api/tenant-content/questions",
            new DirectQuestionWriteDto
            {
                Type = "choice",
                Content = content,
                Status = "Published"
            });

        var responses = await Task.WhenAll(CreateAsync("quota question A"), CreateAsync("quota question B"));
        Assert.Equal(1, responses.Count(response => response.StatusCode == HttpStatusCode.OK));
        Assert.Equal(1, responses.Count(response => response.StatusCode == HttpStatusCode.Conflict));
        var created = responses.Single(response => response.StatusCode == HttpStatusCode.OK);
        var createdJson = await JsonDocument.ParseAsync(await created.Content.ReadAsStreamAsync());
        var questionId = createdJson.RootElement.GetProperty("item").GetProperty("id").GetGuid();

        var archive = await client.PatchAsJsonAsync(
            "/api/tenant-content/questions",
            new DirectQuestionWriteDto
            {
                QuestionId = questionId,
                Type = "choice",
                Content = "archived question",
                Status = "Archived"
            });
        var replacement = await CreateAsync("replacement question");

        Assert.Equal(HttpStatusCode.OK, archive.StatusCode);
        Assert.Equal(HttpStatusCode.OK, replacement.StatusCode);
        using var scope = factory.CreateSystemScope("Verify current question quota");
        var usage = await scope.ServiceProvider.GetRequiredService<TikuDbContext>()
            .TenantFeatureUsages.AsNoTracking()
            .SingleAsync(item => item.TenantId == seed.TenantId && item.MetricCode == SaasQuotaMetricCatalog.PrivateQuestionCount);
        Assert.Equal(1, usage.UsedValue);
    }

    [Fact]
    public async Task Staff_and_student_status_transitions_consume_and_release_current_quotas()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLimitedTenantAsync(factory);
        using var client = factory.CreateClient();
        client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));

        var firstTeacher = await client.PutAsJsonAsync(
            "/api/tenant-admin/members",
            Member("teacher", "13910000001", "Teacher One"));
        var secondTeacher = await client.PutAsJsonAsync(
            "/api/tenant-admin/members",
            Member("teacher", "13910000002", "Teacher Two"));
        Assert.Equal(HttpStatusCode.OK, firstTeacher.StatusCode);
        Assert.Equal(HttpStatusCode.Conflict, secondTeacher.StatusCode);

        var firstStudent = await client.PutAsJsonAsync(
            "/api/tenant-admin/students",
            Student("13920000001", "Student One"));
        var blockedStudent = await client.PutAsJsonAsync(
            "/api/tenant-admin/students",
            Student("13920000002", "Student Two"));
        Assert.Equal(HttpStatusCode.OK, firstStudent.StatusCode);
        Assert.Equal(HttpStatusCode.Conflict, blockedStudent.StatusCode);
        var studentJson = await JsonDocument.ParseAsync(await firstStudent.Content.ReadAsStreamAsync());
        var studentId = studentJson.RootElement.GetProperty("item").GetProperty("userId").GetGuid();

        var disableStudent = await client.PostAsJsonAsync(
            "/api/tenant-admin/students/status",
            new UpdateTenantAdminStudentStatusDto
            {
                UserId = studentId,
                Status = "Disabled",
                Reason = "quota release test"
            });
        var replacementStudent = await client.PutAsJsonAsync(
            "/api/tenant-admin/students",
            Student("13920000002", "Student Two"));

        Assert.Equal(HttpStatusCode.OK, disableStudent.StatusCode);
        Assert.Equal(HttpStatusCode.OK, replacementStudent.StatusCode);
        using var scope = factory.CreateSystemScope("Verify current membership quotas");
        var usages = await scope.ServiceProvider.GetRequiredService<TikuDbContext>()
            .TenantFeatureUsages.AsNoTracking()
            .Where(item => item.TenantId == seed.TenantId)
            .ToDictionaryAsync(item => item.MetricCode, item => item.UsedValue);
        Assert.Equal(1, usages[SaasQuotaMetricCatalog.StaffCount]);
        Assert.Equal(1, usages[SaasQuotaMetricCatalog.StudentCount]);
    }

    [Fact]
    public async Task Missing_current_quota_does_not_block_existing_creation_flows()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var phone = "13730000001";
        await factory.SeedAsync(
            new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Unlimited Tenant" },
            new User { Id = userId, Phone = phone, Name = "Unlimited Admin" }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.TenantAdmin,
                Status = MembershipStatus.Active
            });
        using var client = factory.CreateClient();
        client.UseAccessToken(await client.LoginAsTenantAsync(tenantId, phone));

        var teacher = await client.PutAsJsonAsync(
            "/api/tenant-admin/members",
            Member("teacher", "13930000001", "Unlimited Teacher"));
        var student = await client.PutAsJsonAsync(
            "/api/tenant-admin/students",
            Student("13930000002", "Unlimited Student"));
        var question = await client.PostAsJsonAsync(
            "/api/tenant-content/questions",
            new DirectQuestionWriteDto { Type = "choice", Content = "unlimited question", Status = "Published" });

        Assert.Equal(HttpStatusCode.OK, teacher.StatusCode);
        Assert.Equal(HttpStatusCode.OK, student.StatusCode);
        Assert.Equal(HttpStatusCode.OK, question.StatusCode);
    }

    private static UpsertTenantAdminMemberDto Member(string role, string phone, string name) => new()
    {
        Role = role,
        Status = "Active",
        User = new TenantAdminUserLookupDto { Phone = phone, Name = name }
    };

    private static UpsertTenantAdminStudentDto Student(string phone, string name) => new()
    {
        User = new TenantAdminUserLookupDto { Phone = phone, Name = name }
    };

    private static async Task<(Guid TenantId, string Phone)> SeedLimitedTenantAsync(ApiTestFactory factory)
    {
        var tenantId = Guid.NewGuid();
        var adminId = Guid.NewGuid();
        var offeringId = Guid.NewGuid();
        var versionId = Guid.NewGuid();
        var subscriptionId = Guid.NewGuid();
        var phone = $"136{Random.Shared.Next(10_000_000, 99_999_999)}";
        var now = DateTimeOffset.UtcNow;
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Current Quota Tenant",
                Status = TenantStatus.Active,
                BillingStatus = BillingStatus.Active
            },
            new User { Id = adminId, Phone = phone, Name = "Quota Admin" }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = adminId,
                Role = TenantRole.TenantAdmin,
                Status = MembershipStatus.Active
            },
            Feature(SaasFeatureCatalog.CoreBackoffice, isCore: true),
            Feature(SaasFeatureCatalog.PrivateQuestionBank),
            Feature(SaasFeatureCatalog.StudentManagement),
            new SaasOffering
            {
                Id = offeringId,
                Code = $"current-quota-{tenantId:N}",
                Name = "Current Quota Plan",
                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)
            },
            Entitlement(versionId, SaasFeatureCatalog.PrivateQuestionBank),
            Entitlement(versionId, SaasFeatureCatalog.StudentManagement),
            LimitDefinition(SaasQuotaMetricCatalog.StaffCount, SaasFeatureCatalog.CoreBackoffice),
            LimitDefinition(SaasQuotaMetricCatalog.StudentCount, SaasFeatureCatalog.StudentManagement),
            LimitDefinition(SaasQuotaMetricCatalog.PrivateQuestionCount, SaasFeatureCatalog.PrivateQuestionBank),
            LimitDefinition(SaasQuotaMetricCatalog.StorageBytes, SaasFeatureCatalog.PrivateQuestionBank),
            Limit(versionId, SaasQuotaMetricCatalog.StaffCount),
            Limit(versionId, SaasQuotaMetricCatalog.StudentCount),
            Limit(versionId, SaasQuotaMetricCatalog.PrivateQuestionCount),
            Limit(versionId, SaasQuotaMetricCatalog.StorageBytes),
            new TenantSaasSubscription
            {
                Id = subscriptionId,
                TenantId = tenantId,
                BaseOfferingVersionId = versionId,
                Status = TenantSaasSubscriptionStatus.Active,
                StartsAt = now.AddDays(-1),
                CurrentPeriodStart = now.AddDays(-1),
                CurrentPeriodEnd = now.AddMonths(1)
            },
            new TenantSaasSubscriptionItem
            {
                TenantId = tenantId,
                SubscriptionId = subscriptionId,
                OfferingVersionId = versionId,
                ItemType = TenantSaasSubscriptionItemType.BasePlan,
                Status = TenantSaasSubscriptionItemStatus.Active,
                StartsAt = now.AddDays(-1),
                EndsAt = now.AddMonths(1)
            });
        return (tenantId, phone);
    }

    private static SaasFeature Feature(string code, bool isCore = false) => new()
    {
        Code = code,
        Name = code,
        Category = "integration",
        Status = SaasFeatureStatus.Active,
        IsCore = isCore
    };

    private static SaasOfferingVersionFeature Entitlement(Guid versionId, string featureCode) => new()
    {
        OfferingVersionId = versionId,
        FeatureCode = featureCode
    };

    private static SaasFeatureLimitDefinition LimitDefinition(string metricCode, string featureCode) => new()
    {
        MetricCode = metricCode,
        FeatureCode = featureCode,
        Name = metricCode,
        Unit = "count",
        Kind = SaasFeatureLimitKind.Current,
        WarningPercent = 80,
        IsHardLimit = true
    };

    private static SaasOfferingVersionLimit Limit(Guid versionId, string metricCode) => new()
    {
        OfferingVersionId = versionId,
        MetricCode = metricCode,
        LimitValue = 1
    };

    private static async Task<Dictionary<string, long>> ReadUsageVersionsAsync(ApiTestFactory factory, Guid tenantId)
    {
        using var scope = factory.CreateSystemScope("Read reconciled feature usage versions");
        return await scope.ServiceProvider.GetRequiredService<TikuDbContext>().TenantFeatureUsages.AsNoTracking()
            .Where(item => item.TenantId == tenantId)
            .ToDictionaryAsync(item => item.MetricCode, item => item.Version);
    }
}
