using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Learning;
using Tiku.Application.QuestionBanks;
using Tiku.Application.Security;
using Tiku.Domain.Catalog;
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;
using Tiku.IntegrationTests.Api;

namespace Tiku.IntegrationTests;

public sealed class PhaseThreeTenantIsolationTests
{
    [Fact]
    public async Task Unresolved_context_reads_no_tenant_rows_and_rejects_writes()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        await factory.SeedAsync(Tenant(tenantId, "tenant-a"), new TenantBranding
        {
            TenantId = tenantId,
            BrandName = "Tenant A"
        });

        using var scope = factory.Services.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Empty(await dbContext.TenantBrandings.ToArrayAsync());
        dbContext.TenantBrandings.Add(new TenantBranding { BrandName = "unresolved" });
        await Assert.ThrowsAsync<TenantIsolationException>(() => dbContext.SaveChangesAsync());
    }

    [Fact]
    public async Task Tenant_a_cannot_read_or_attach_tenant_b_data()
    {
        await using var factory = new ApiTestFactory();
        var tenantA = Guid.NewGuid();
        var tenantB = Guid.NewGuid();
        await factory.SeedAsync(
            Tenant(tenantA, "tenant-a"),
            Tenant(tenantB, "tenant-b"),
            new TenantBranding { TenantId = tenantA, BrandName = "A" },
            new TenantBranding { TenantId = tenantB, BrandName = "B" });

        using var scope = factory.CreateTenantScope(tenantA, "tenant-a");
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var visible = await dbContext.TenantBrandings.AsNoTracking().ToArrayAsync();
        Assert.Equal(tenantA, Assert.Single(visible).TenantId);

        var forged = new TenantBranding { TenantId = tenantB, BrandName = "forged" };
        dbContext.Attach(forged);
        forged.BrandName = "modified";
        dbContext.Entry(forged).Property(item => item.BrandName).IsModified = true;
        await Assert.ThrowsAsync<TenantIsolationException>(() => dbContext.SaveChangesAsync());
    }

    [Fact]
    public async Task PostgreSql_rejects_reference_to_another_tenant_private_question()
    {
        await using var factory = new ApiTestFactory();
        var tenantA = Guid.NewGuid();
        var tenantB = Guid.NewGuid();
        var questionId = Guid.NewGuid();
        await factory.SeedAsync(
            Tenant(tenantA, "tenant-a"),
            Tenant(tenantB, "tenant-b"),
            new Question
            {
                Id = questionId,
                TenantId = tenantB,
                Type = "choice",
                Status = QuestionStatus.Published
            });

        await Assert.ThrowsAsync<DbUpdateException>(() => factory.SeedAsync(new TenantQuestionReference
        {
            TenantId = tenantA,
            QuestionOwnerTenantId = tenantB,
            QuestionId = questionId,
            Source = QuestionSource.Tenant
        }));
    }

    [Fact]
    public async Task Taxonomy_extension_accepts_platform_parent_and_rejects_other_tenant_parent()
    {
        await using var factory = new ApiTestFactory();
        var platformId = Guid.NewGuid();
        var tenantA = Guid.NewGuid();
        var tenantB = Guid.NewGuid();
        var platformNodeId = Guid.NewGuid();
        var tenantBNodeId = Guid.NewGuid();
        await factory.SeedAsync(
            Tenant(platformId, "platform", TenantMode.PlatformOwned),
            Tenant(tenantA, "tenant-a"),
            Tenant(tenantB, "tenant-b"),
            new TaxonomyNode
            {
                Id = platformNodeId,
                TenantId = platformId,
                Code = "platform-subject",
                Name = "Platform Subject",
                NodeType = TaxonomyNodeType.Subject
            },
            new TaxonomyNode
            {
                Id = tenantBNodeId,
                TenantId = tenantB,
                Code = "tenant-b-subject",
                Name = "Tenant B Subject",
                NodeType = TaxonomyNodeType.Subject
            });

        await factory.SeedAsync(new TaxonomyNode
        {
            TenantId = tenantA,
            ParentOwnerTenantId = platformId,
            ParentId = platformNodeId,
            Code = "tenant-a-platform-extension",
            Name = "Valid Extension",
            NodeType = TaxonomyNodeType.Chapter
        });

        await Assert.ThrowsAsync<DbUpdateException>(() => factory.SeedAsync(new TaxonomyNode
        {
            TenantId = tenantA,
            ParentOwnerTenantId = tenantB,
            ParentId = tenantBNodeId,
            Code = "tenant-a-forged-extension",
            Name = "Invalid Extension",
            NodeType = TaxonomyNodeType.Chapter
        }));
    }

    [Theory]
    [InlineData(TenantSaasSubscriptionStatus.Trial, true)]
    [InlineData(TenantSaasSubscriptionStatus.Active, true)]
    [InlineData(TenantSaasSubscriptionStatus.PastDue, false)]
    [InlineData(TenantSaasSubscriptionStatus.Cancelled, false)]
    public async Task Public_question_reference_requires_current_subscription(
        TenantSaasSubscriptionStatus subscriptionStatus,
        bool allowed)
    {
        await using var factory = new ApiTestFactory();
        var platformId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var questionId = Guid.NewGuid();
        var offeringId = Guid.NewGuid();
        var versionId = Guid.NewGuid();
        var subscriptionId = Guid.NewGuid();
        var now = DateTimeOffset.UtcNow;
        await factory.SeedAsync(
            Tenant(platformId, "platform", TenantMode.PlatformOwned),
            Tenant(tenantId, "tenant-a"),
            new Question
            {
                Id = questionId,
                TenantId = platformId,
                Type = "choice",
                Status = QuestionStatus.Published
            },
            new SaasFeature
            {
                Code = SaasFeatureCatalog.Practice,
                Name = "Practice",
                Category = "learning",
                Status = SaasFeatureStatus.Active
            },
            new SaasOffering
            {
                Id = offeringId, Code = "standard", Name = "Standard", Type = SaasOfferingType.BasePlan,
                Status = SaasOfferingStatus.Active
            },
            new SaasOfferingVersion
            {
                Id = versionId,
                OfferingId = offeringId,
                Status = SaasOfferingVersionStatus.Published,
                OriginalAmountCents = 100,
                AmountCents = 100,
                PublishedAt = now.AddDays(-1)
            },
            new SaasOfferingVersionFeature { OfferingVersionId = versionId, FeatureCode = SaasFeatureCatalog.Practice },
            new TenantSaasSubscription
            {
                Id = subscriptionId,
                TenantId = tenantId,
                BaseOfferingVersionId = versionId,
                Status = subscriptionStatus,
                StartsAt = now.AddDays(-1),
                CurrentPeriodStart = now.AddDays(-1),
                CurrentPeriodEnd = now.AddDays(1)
            },
            new TenantSaasSubscriptionItem
            {
                TenantId = tenantId,
                SubscriptionId = subscriptionId,
                OfferingVersionId = versionId,
                Status = TenantSaasSubscriptionItemStatus.Active,
                StartsAt = now.AddDays(-1),
                EndsAt = now.AddDays(1)
            });

        using var scope = factory.CreateTenantScope(tenantId, "tenant-a");
        var service = scope.ServiceProvider.GetRequiredService<IQuestionReferenceService>();
        var operation = async () =>
        {
            var reference = await service.ResolveAsync(
                tenantId,
                null,
                new QuestionLocator(QuestionSource.Platform, questionId));
            await scope.ServiceProvider.GetRequiredService<TikuDbContext>().SaveChangesAsync();
            return reference;
        };

        if (allowed)
        {
            var reference = await operation();
            Assert.Equal(platformId, reference.QuestionOwnerTenantId);
        }
        else
        {
            await Assert.ThrowsAsync<PublicQuestionAccessDeniedException>(operation);
        }
    }

    [Fact]
    public async Task Mixed_collection_pins_versions_and_new_session_uses_new_platform_version()
    {
        await using var factory = new ApiTestFactory();
        var platformId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var platformQuestionId = Guid.NewGuid();
        var privateQuestionId = Guid.NewGuid();
        var platformV1 = Guid.NewGuid();
        var platformV2 = Guid.NewGuid();
        var privateV1 = Guid.NewGuid();
        var collectionId = Guid.NewGuid();
        var platformReferenceId = Guid.NewGuid();
        var privateReferenceId = Guid.NewGuid();
        await factory.SeedAsync(
            Tenant(platformId, "platform", TenantMode.PlatformOwned),
            Tenant(tenantId, "tenant-a"),
            new User { Id = userId, Phone = "13800009999" });
        await factory.SeedQuestionWithVersionAsync(
            new Question
            {
                Id = platformQuestionId,
                TenantId = platformId,
                Type = "choice",
                Status = QuestionStatus.Published
            },
            new QuestionVersion
            {
                Id = platformV1,
                TenantId = platformId,
                QuestionId = platformQuestionId,
                VersionNo = 1,
                Content = "platform-v1",
                CorrectOptionIndex = 0
            });
        await factory.SeedQuestionWithVersionAsync(
            new Question
            {
                Id = privateQuestionId,
                TenantId = tenantId,
                Type = "choice",
                Status = QuestionStatus.Published
            },
            new QuestionVersion
            {
                Id = privateV1,
                TenantId = tenantId,
                QuestionId = privateQuestionId,
                VersionNo = 1,
                Content = "private-v1",
                CorrectOptionIndex = 0
            });
        await factory.SeedAsync(
            new TenantQuestionReference
            {
                Id = platformReferenceId,
                TenantId = tenantId,
                QuestionOwnerTenantId = platformId,
                QuestionId = platformQuestionId,
                Source = QuestionSource.Platform
            },
            new TenantQuestionReference
            {
                Id = privateReferenceId,
                TenantId = tenantId,
                QuestionOwnerTenantId = tenantId,
                QuestionId = privateQuestionId,
                Source = QuestionSource.Tenant
            },
            new QuestionCollection
            {
                Id = collectionId,
                TenantId = tenantId,
                Name = "mixed",
                Status = ContentStatus.Active
            },
            new QuestionCollectionItem
            {
                TenantId = tenantId,
                CollectionId = collectionId,
                QuestionReferenceId = platformReferenceId,
                QuestionOwnerTenantId = platformId,
                QuestionId = platformQuestionId,
                SortOrder = 1
            },
            new QuestionCollectionItem
            {
                TenantId = tenantId,
                CollectionId = collectionId,
                QuestionReferenceId = privateReferenceId,
                QuestionOwnerTenantId = tenantId,
                QuestionId = privateQuestionId,
                SortOrder = 2
            });

        Guid firstSessionId;
        using (var scope = factory.CreateTenantScope(tenantId, "tenant-a"))
        {
            var created = await scope.ServiceProvider.GetRequiredService<ILearningActivityService>()
                .CreatePracticeSessionAsync(
                    new LearningActor(tenantId, userId),
                    new PracticeSessionCommand(
                        "collection", null, null, null, collectionId, null, null, 10, null, null,
                        JsonDefaults.Object()));
            firstSessionId = created.Id;
        }

        await factory.SeedAsync(new QuestionVersion
        {
            Id = platformV2,
            TenantId = platformId,
            QuestionId = platformQuestionId,
            VersionNo = 2,
            Content = "platform-v2",
            CorrectOptionIndex = 0
        });
        using (var scope = factory.CreateSystemScope("Publish platform question V2"))
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
            var question = await dbContext.Questions.SingleAsync(item =>
                item.TenantId == platformId && item.Id == platformQuestionId);
            question.CurrentVersionId = platformV2;
            await dbContext.SaveChangesAsync();
        }

        Guid secondSessionId;
        using (var scope = factory.CreateTenantScope(tenantId, "tenant-a"))
        {
            var created = await scope.ServiceProvider.GetRequiredService<ILearningActivityService>()
                .CreatePracticeSessionAsync(
                    new LearningActor(tenantId, userId),
                    new PracticeSessionCommand(
                        "collection", null, null, null, collectionId, null, null, 10, null, null,
                        JsonDefaults.Object()));
            secondSessionId = created.Id;
        }

        using var verificationScope = factory.CreateSystemScope();
        var verificationDb = verificationScope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var firstQuestions = await verificationDb.PracticeSessionQuestions
            .Where(item => item.PracticeSessionId == firstSessionId)
            .ToArrayAsync();
        var secondQuestions = await verificationDb.PracticeSessionQuestions
            .Where(item => item.PracticeSessionId == secondSessionId)
            .ToArrayAsync();
        Assert.Equal(2, firstQuestions.Length);
        Assert.Equal(2, secondQuestions.Length);
        Assert.Equal(platformV1,
            firstQuestions.Single(item => item.QuestionId == platformQuestionId).QuestionVersionId);
        Assert.Equal(platformV2,
            secondQuestions.Single(item => item.QuestionId == platformQuestionId).QuestionVersionId);
        Assert.Equal(privateV1, firstQuestions.Single(item => item.QuestionId == privateQuestionId).QuestionVersionId);
    }

    private static Tenant Tenant(Guid id, string slug, TenantMode mode = TenantMode.Saas)
    {
        return new Tenant
        {
            Id = id,
            Slug = slug,
            Name = slug,
            Status = TenantStatus.Active,
            Mode = mode
        };
    }
}