using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using System.Reflection;
using Tiku.Application.Security;
using Tiku.Domain.Catalog;
using Tiku.Domain.Commerce;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Growth;
using Tiku.Domain.Identity;
using Tiku.Domain.Import;
using Tiku.Domain.Learning;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;

namespace Tiku.Infrastructure.Persistence;

public sealed class TikuDbContext(
    DbContextOptions<TikuDbContext> options,
    ITenantContext tenantContext) : IdentityUserContext<User, Guid>(options), IDataProtectionKeyContext
{
    public TikuDbContext(DbContextOptions<TikuDbContext> options)
        : this(options, CreateToolingTenantContext())
    {
    }

    public Guid? CurrentTenantId => tenantContext.TenantId;
    public Guid CurrentTenantIdOrEmpty => tenantContext.TenantId ?? Guid.Empty;
    public bool IsTenantResolved => tenantContext.IsResolved;
    public bool IsSystemScope => tenantContext.IsSystem;
    public long SaveVersion { get; private set; }

    private static ITenantContext CreateToolingTenantContext()
    {
        var context = new TenantContext();
        context.InitializeSystem(null, "Direct DbContext construction for model tooling");
        return context;
    }
    public DbSet<Tenant> Tenants => Set<Tenant>();
    public new DbSet<User> Users => Set<User>();
    public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();
    public DbSet<UserIdentity> UserIdentities => Set<UserIdentity>();
    public DbSet<TenantMembership> TenantMemberships => Set<TenantMembership>();
    public DbSet<TenantDomain> TenantDomains => Set<TenantDomain>();
    public DbSet<TenantBranding> TenantBrandings => Set<TenantBranding>();
    public DbSet<TenantSettings> TenantSettings => Set<TenantSettings>();
    public DbSet<TenantAuthPolicy> TenantAuthPolicies => Set<TenantAuthPolicy>();
    public DbSet<TenantFrontendConfig> TenantFrontendConfigs => Set<TenantFrontendConfig>();
    public DbSet<TenantExternalProvider> TenantExternalProviders => Set<TenantExternalProvider>();
    public DbSet<TenantSecret> TenantSecrets => Set<TenantSecret>();
    public DbSet<SmsVerificationCode> SmsVerificationCodes => Set<SmsVerificationCode>();
    public DbSet<SmsChannel> SmsChannels => Set<SmsChannel>();
    public DbSet<SmsTemplate> SmsTemplates => Set<SmsTemplate>();
    public DbSet<SmsSendLog> SmsSendLogs => Set<SmsSendLog>();
    public DbSet<AuthLoginEvent> AuthLoginEvents => Set<AuthLoginEvent>();
    public DbSet<AuthSession> AuthSessions => Set<AuthSession>();
    public DbSet<AuthChallenge> AuthChallenges => Set<AuthChallenge>();
    public DbSet<SmsSendRateLimit> SmsSendRateLimits => Set<SmsSendRateLimit>();
    public DbSet<TenantClass> TenantClasses => Set<TenantClass>();
    public DbSet<TenantClassMember> TenantClassMembers => Set<TenantClassMember>();
    public DbSet<TenantStudentNote> TenantStudentNotes => Set<TenantStudentNote>();
    public DbSet<TenantStudentFollowup> TenantStudentFollowups => Set<TenantStudentFollowup>();
    public DbSet<StudentProfile> StudentProfiles => Set<StudentProfile>();
    public DbSet<Region> Regions => Set<Region>();
    public DbSet<RegionModule> RegionModules => Set<RegionModule>();
    public DbSet<ModuleNode> ModuleNodes => Set<ModuleNode>();
    public DbSet<School> Schools => Set<School>();
    public DbSet<Major> Majors => Set<Major>();
    public DbSet<Subject> Subjects => Set<Subject>();
    public DbSet<Category> Categories => Set<Category>();
    public DbSet<TaxonomyNode> TaxonomyNodes => Set<TaxonomyNode>();
    public DbSet<QuestionTaxonomyAssignment> QuestionTaxonomyAssignments => Set<QuestionTaxonomyAssignment>();
    public DbSet<ScorelineField> ScorelineFields => Set<ScorelineField>();
    public DbSet<ScorelineRecord> ScorelineRecords => Set<ScorelineRecord>();
    public DbSet<QuestionBank> QuestionBanks => Set<QuestionBank>();
    public DbSet<Question> Questions => Set<Question>();
    public DbSet<QuestionVersion> QuestionVersions => Set<QuestionVersion>();
    public DbSet<ContentEntry> ContentEntries => Set<ContentEntry>();
    public DbSet<ContentNode> ContentNodes => Set<ContentNode>();
    public DbSet<QuestionCollection> QuestionCollections => Set<QuestionCollection>();
    public DbSet<QuestionCollectionItem> QuestionCollectionItems => Set<QuestionCollectionItem>();
    public DbSet<PracticeBlueprint> PracticeBlueprints => Set<PracticeBlueprint>();
    public DbSet<VocabularyUnit> VocabularyUnits => Set<VocabularyUnit>();
    public DbSet<VocabularyWord> VocabularyWords => Set<VocabularyWord>();
    public DbSet<UserWordProgress> UserWordProgress => Set<UserWordProgress>();
    public DbSet<UserWordFavorite> UserWordFavorites => Set<UserWordFavorite>();
    public DbSet<HandbookSubject> HandbookSubjects => Set<HandbookSubject>();
    public DbSet<HandbookChapter> HandbookChapters => Set<HandbookChapter>();
    public DbSet<HandbookEntry> HandbookEntries => Set<HandbookEntry>();
    public DbSet<QuestionTypeGroup> QuestionTypeGroups => Set<QuestionTypeGroup>();
    public DbSet<SubjectShare> SubjectShares => Set<SubjectShare>();
    public DbSet<ContentAsset> ContentAssets => Set<ContentAsset>();
    public DbSet<ContentAssetAccessEvent> ContentAssetAccessEvents => Set<ContentAssetAccessEvent>();
    public DbSet<ContentAssetSecurityScanEvent> ContentAssetSecurityScanEvents => Set<ContentAssetSecurityScanEvent>();
    public DbSet<ContentImportJob> ContentImportJobs => Set<ContentImportJob>();
    public DbSet<ContentImportItem> ContentImportItems => Set<ContentImportItem>();
    public DbSet<ContentImportIssue> ContentImportIssues => Set<ContentImportIssue>();
    public DbSet<ImageAsset> Images => Set<ImageAsset>();
    public DbSet<AppAsset> AppAssets => Set<AppAsset>();
    public DbSet<VideoExplanation> VideoExplanations => Set<VideoExplanation>();
    public DbSet<QuestionVideo> QuestionVideos => Set<QuestionVideo>();
    public DbSet<VideoPlaybackProgress> VideoPlaybackProgress => Set<VideoPlaybackProgress>();
    public DbSet<TenantQuestionBankPreference> TenantQuestionBankPreferences => Set<TenantQuestionBankPreference>();
    public DbSet<TenantQuestionReference> TenantQuestionReferences => Set<TenantQuestionReference>();
    public DbSet<AiRecommendationReport> AiRecommendationReports => Set<AiRecommendationReport>();
    public DbSet<PracticeSession> PracticeSessions => Set<PracticeSession>();
    public DbSet<PracticeSessionQuestion> PracticeSessionQuestions => Set<PracticeSessionQuestion>();
    public DbSet<AnswerRecord> AnswerRecords => Set<AnswerRecord>();
    public DbSet<FavoriteQuestion> FavoriteQuestions => Set<FavoriteQuestion>();
    public DbSet<WrongQuestion> WrongQuestions => Set<WrongQuestion>();
    public DbSet<RecentPractice> RecentPractices => Set<RecentPractice>();
    public DbSet<ExamDate> ExamDates => Set<ExamDate>();
    public DbSet<Report> Reports => Set<Report>();
    public DbSet<ReportStatusEvent> ReportStatusEvents => Set<ReportStatusEvent>();
    public DbSet<UserScoreEvent> UserScoreEvents => Set<UserScoreEvent>();
    public DbSet<PracticeDailyUsage> PracticeDailyUsages => Set<PracticeDailyUsage>();
    public DbSet<PracticeAccessEvent> PracticeAccessEvents => Set<PracticeAccessEvent>();
    public DbSet<PracticeSessionReport> PracticeSessionReports => Set<PracticeSessionReport>();
    public DbSet<PracticeSessionReportSection> PracticeSessionReportSections => Set<PracticeSessionReportSection>();
    public DbSet<DashboardDailyStat> DashboardDailyStats => Set<DashboardDailyStat>();
    public DbSet<RevenueDailyStat> RevenueDailyStats => Set<RevenueDailyStat>();
    public DbSet<Product> Products => Set<Product>();
    public DbSet<SvipPlan> SvipPlans => Set<SvipPlan>();
    public DbSet<Order> Orders => Set<Order>();
    public DbSet<OrderItem> OrderItems => Set<OrderItem>();
    public DbSet<Payment> Payments => Set<Payment>();
    public DbSet<PaymentEvent> PaymentEvents => Set<PaymentEvent>();
    public DbSet<Entitlement> Entitlements => Set<Entitlement>();
    public DbSet<CodeBatch> CodeBatches => Set<CodeBatch>();
    public DbSet<ActivationCode> ActivationCodes => Set<ActivationCode>();
    public DbSet<Coupon> Coupons => Set<Coupon>();
    public DbSet<CouponRedemption> CouponRedemptions => Set<CouponRedemption>();
    public DbSet<CommerceRefundRequest> CommerceRefundRequests => Set<CommerceRefundRequest>();
    public DbSet<CommerceRefundEvent> CommerceRefundEvents => Set<CommerceRefundEvent>();
    public DbSet<CommerceReconciliationBatch> CommerceReconciliationBatches => Set<CommerceReconciliationBatch>();
    public DbSet<CommerceReconciliationItem> CommerceReconciliationItems => Set<CommerceReconciliationItem>();
    public DbSet<CommerceReconciliationIssue> CommerceReconciliationIssues => Set<CommerceReconciliationIssue>();
    public DbSet<CommerceReconciliationIssueEvent> CommerceReconciliationIssueEvents => Set<CommerceReconciliationIssueEvent>();
    public DbSet<CommerceAdjustmentVoucher> CommerceAdjustmentVouchers => Set<CommerceAdjustmentVoucher>();
    public DbSet<CommerceAdjustmentVoucherEvent> CommerceAdjustmentVoucherEvents => Set<CommerceAdjustmentVoucherEvent>();
    public DbSet<PointActivityTask> PointActivityTasks => Set<PointActivityTask>();
    public DbSet<PointActivityClaim> PointActivityClaims => Set<PointActivityClaim>();
    public DbSet<PointExchangeItem> PointExchangeItems => Set<PointExchangeItem>();
    public DbSet<PointExchangeOrder> PointExchangeOrders => Set<PointExchangeOrder>();
    public DbSet<ReferralTrack> ReferralTracks => Set<ReferralTrack>();
    public DbSet<ReferralCode> ReferralCodes => Set<ReferralCode>();
    public DbSet<ReferralLead> ReferralLeads => Set<ReferralLead>();
    public DbSet<ReferralTeamEdge> ReferralTeamEdges => Set<ReferralTeamEdge>();
    public DbSet<ReferralQrcode> ReferralQrcodes => Set<ReferralQrcode>();
    public DbSet<CrmConfig> CrmConfigs => Set<CrmConfig>();
    public DbSet<CrmWebhookQueueItem> CrmWebhookQueue => Set<CrmWebhookQueueItem>();
    public DbSet<CrmWebhookLog> CrmWebhookLogs => Set<CrmWebhookLog>();
    public DbSet<TenantCommissionSetting> TenantCommissionSettings => Set<TenantCommissionSetting>();
    public DbSet<CommissionSettlement> CommissionSettlements => Set<CommissionSettlement>();
    public DbSet<CommissionSettlementItem> CommissionSettlementItems => Set<CommissionSettlementItem>();
    public DbSet<CommissionSettlementProof> CommissionSettlementProofs => Set<CommissionSettlementProof>();
    public DbSet<CommissionSettlementExportEvent> CommissionSettlementExportEvents => Set<CommissionSettlementExportEvent>();
    public DbSet<Banner> Banners => Set<Banner>();
    public DbSet<Faq> Faqs => Set<Faq>();
    public DbSet<Announcement> Announcements => Set<Announcement>();
    public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
    public DbSet<BackendPermission> BackendPermissions => Set<BackendPermission>();
    public DbSet<BackendMenu> BackendMenus => Set<BackendMenu>();
    public DbSet<TenantBackendRole> TenantBackendRoles => Set<TenantBackendRole>();
    public DbSet<TenantBackendRolePermission> TenantBackendRolePermissions => Set<TenantBackendRolePermission>();
    public DbSet<TenantBackendRoleMenu> TenantBackendRoleMenus => Set<TenantBackendRoleMenu>();
    public DbSet<TenantBackendUserRole> TenantBackendUserRoles => Set<TenantBackendUserRole>();
    public DbSet<PlatformBackendRole> PlatformBackendRoles => Set<PlatformBackendRole>();
    public DbSet<PlatformBackendRolePermission> PlatformBackendRolePermissions => Set<PlatformBackendRolePermission>();
    public DbSet<PlatformBackendRoleMenu> PlatformBackendRoleMenus => Set<PlatformBackendRoleMenu>();
    public DbSet<PlatformBackendUserRole> PlatformBackendUserRoles => Set<PlatformBackendUserRole>();
    public DbSet<BackgroundJob> BackgroundJobs => Set<BackgroundJob>();
    public DbSet<UserNotification> UserNotifications => Set<UserNotification>();
    public DbSet<Badge> Badges => Set<Badge>();
    public DbSet<UserBadge> UserBadges => Set<UserBadge>();
    public DbSet<TenantContentNotification> TenantContentNotifications => Set<TenantContentNotification>();
    public DbSet<TenantThemeTemplate> TenantThemeTemplates => Set<TenantThemeTemplate>();
    public DbSet<TenantThemeConfig> TenantThemeConfigs => Set<TenantThemeConfig>();
    public DbSet<TenantBillingProfile> TenantBillingProfiles => Set<TenantBillingProfile>();
    public DbSet<PlatformBillingInvoiceReminder> PlatformBillingInvoiceReminders => Set<PlatformBillingInvoiceReminder>();
    public DbSet<PlatformAuditAlertRule> PlatformAuditAlertRules => Set<PlatformAuditAlertRule>();
    public DbSet<PlatformAuditAlert> PlatformAuditAlerts => Set<PlatformAuditAlert>();
    public DbSet<PlatformBillingDunningNotificationChannel> PlatformBillingDunningNotificationChannels => Set<PlatformBillingDunningNotificationChannel>();
    public DbSet<PlatformBillingDunningNotificationEvent> PlatformBillingDunningNotificationEvents => Set<PlatformBillingDunningNotificationEvent>();
    public DbSet<PlatformPaymentApp> PlatformPaymentApps => Set<PlatformPaymentApp>();
    public DbSet<PlatformPaymentChannel> PlatformPaymentChannels => Set<PlatformPaymentChannel>();
    public DbSet<SaasFeature> SaasFeatures => Set<SaasFeature>();
    public DbSet<PermissionModule> PermissionModules => Set<PermissionModule>();
    public DbSet<SaasOffering> SaasOfferings => Set<SaasOffering>();
    public DbSet<SaasOfferingVersion> SaasOfferingVersions => Set<SaasOfferingVersion>();
    public DbSet<SaasOfferingVersionFeature> SaasOfferingVersionFeatures => Set<SaasOfferingVersionFeature>();
    public DbSet<SaasFeatureLimitDefinition> SaasFeatureLimitDefinitions => Set<SaasFeatureLimitDefinition>();
    public DbSet<SaasOfferingVersionLimit> SaasOfferingVersionLimits => Set<SaasOfferingVersionLimit>();
    public DbSet<TenantSaasSubscription> TenantSaasSubscriptions => Set<TenantSaasSubscription>();
    public DbSet<TenantSaasSubscriptionItem> TenantSaasSubscriptionItems => Set<TenantSaasSubscriptionItem>();
    public DbSet<TenantFeatureOverride> TenantFeatureOverrides => Set<TenantFeatureOverride>();
    public DbSet<TenantFeatureUsage> TenantFeatureUsages => Set<TenantFeatureUsage>();
    public DbSet<PlatformBillingQuote> PlatformBillingQuotes => Set<PlatformBillingQuote>();
    public DbSet<PlatformBillingQuoteItem> PlatformBillingQuoteItems => Set<PlatformBillingQuoteItem>();
    public DbSet<PlatformBillingOrder> PlatformBillingOrders => Set<PlatformBillingOrder>();
    public DbSet<PlatformBillingOrderItem> PlatformBillingOrderItems => Set<PlatformBillingOrderItem>();
    public DbSet<PlatformBillingPayment> PlatformBillingPayments => Set<PlatformBillingPayment>();
    public DbSet<PlatformBillingPaymentEvent> PlatformBillingPaymentEvents => Set<PlatformBillingPaymentEvent>();
    public DbSet<PlatformBillingRefund> PlatformBillingRefunds => Set<PlatformBillingRefund>();
    public DbSet<PlatformBillingInvoice> PlatformBillingInvoices => Set<PlatformBillingInvoice>();
    public DbSet<PocketBaseImportRun> PocketBaseImportRuns => Set<PocketBaseImportRun>();
    public DbSet<PocketBaseRawRecord> PocketBaseRawRecords => Set<PocketBaseRawRecord>();
    public DbSet<PocketBaseImportIssue> PocketBaseImportIssues => Set<PocketBaseImportIssue>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<IdentityUserClaim<Guid>>().ToTable("user_claims");
        modelBuilder.Entity<IdentityUserLogin<Guid>>().ToTable("user_logins");
        modelBuilder.Entity<IdentityUserToken<Guid>>().ToTable("user_tokens");
        modelBuilder.HasPostgresExtension("citext");
        modelBuilder.HasPostgresExtension("ltree");
        modelBuilder.HasPostgresExtension("pg_trgm");
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(TikuDbContext).Assembly);
        modelBuilder.Entity<DataProtectionKey>().ToTable("data_protection_keys");
        ApplyTenantQueryFilters(modelBuilder);
        ValidateTenantModel(modelBuilder);
        modelBuilder.UseSnakeCaseIdentifiers();
    }

    private void ApplyTenantQueryFilters(ModelBuilder modelBuilder)
    {
        var applyMethod = typeof(TikuDbContext)
            .GetMethod(nameof(ApplyTenantQueryFilter), BindingFlags.Instance | BindingFlags.NonPublic)!;

        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            var tenantProperty = entityType.FindProperty("TenantId");
            if (tenantProperty?.ClrType != typeof(Guid) || entityType.BaseType is not null)
            {
                continue;
            }

            applyMethod.MakeGenericMethod(entityType.ClrType).Invoke(this, [modelBuilder]);
        }
    }

    private void ApplyTenantQueryFilter<TEntity>(ModelBuilder modelBuilder)
        where TEntity : class
    {
        modelBuilder.Entity<TEntity>().HasQueryFilter(entity =>
            IsSystemScope ||
            (IsTenantResolved &&
             EF.Property<Guid>(entity, "TenantId") == CurrentTenantIdOrEmpty));
    }

    private static void ValidateTenantModel(ModelBuilder modelBuilder)
    {
        var invalidUniqueIndexes = new List<string>();
        var invalidTenantForeignKeys = new List<string>();
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            var tenantProperty = entityType.FindProperty("TenantId");
            if (tenantProperty?.ClrType != typeof(Guid))
            {
                continue;
            }

            if (!typeof(ITenantOwned).IsAssignableFrom(entityType.ClrType))
            {
                throw new InvalidOperationException(
                    $"Tenant entity '{entityType.ClrType.Name}' must implement {nameof(ITenantOwned)}.");
            }

            if (!entityType.GetDeclaredQueryFilters().Any())
            {
                throw new InvalidOperationException(
                    $"Tenant entity '{entityType.ClrType.Name}' does not have a tenant query filter.");
            }

            foreach (var index in entityType.GetDeclaredIndexes().Where(index => index.IsUnique))
            {
                if (index.Properties.All(property => property.Name != "TenantId") &&
                    index.FindAnnotation("Tiku:GlobalUnique")?.Value is not true)
                {
                    invalidUniqueIndexes.Add(
                        $"{entityType.ClrType.Name}({string.Join(",", index.Properties.Select(property => property.Name))})");
                }
            }


            foreach (var foreignKey in entityType.GetDeclaredForeignKeys().Where(foreignKey =>
                         typeof(ITenantOwned).IsAssignableFrom(foreignKey.PrincipalEntityType.ClrType)))
            {
                if (foreignKey.PrincipalKey.Properties.All(property => property.Name != "TenantId"))
                {
                    invalidTenantForeignKeys.Add(
                        $"{entityType.ClrType.Name}->{foreignKey.PrincipalEntityType.ClrType.Name}");
                }
            }
        }

        if (invalidUniqueIndexes.Count > 0)
        {
            throw new InvalidOperationException(
                $"Unique indexes on tenant entities must include TenantId: {string.Join("; ", invalidUniqueIndexes)}");
        }


        if (invalidTenantForeignKeys.Count > 0)
        {
            throw new InvalidOperationException(
                $"Foreign keys between tenant entities must use a tenant-qualified principal key: {string.Join("; ", invalidTenantForeignKeys)}");
        }
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        UpdateTimestamps();
        var result = base.SaveChanges(acceptAllChangesOnSuccess);
        SaveVersion++;
        return result;
    }

    public override async Task<int> SaveChangesAsync(
        bool acceptAllChangesOnSuccess,
        CancellationToken cancellationToken = default)
    {
        UpdateTimestamps();
        var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        SaveVersion++;
        return result;
    }

    private void UpdateTimestamps()
    {
        var now = DateTimeOffset.UtcNow;

        foreach (var entry in ChangeTracker.Entries<User>().Where(entry => entry.State == EntityState.Modified))
        {
            if (entry.Property(user => user.Status).IsModified ||
                entry.Property(user => user.PasswordHash).IsModified)
            {
                entry.Entity.SecurityStamp = Guid.NewGuid().ToString("N");
            }
        }

        foreach (var entry in ChangeTracker.Entries<IHasTimestamps>())
        {
            if (entry.State == EntityState.Added)
            {
                entry.Entity.CreatedAt = now;
            }

            if (entry.State is EntityState.Added or EntityState.Modified)
            {
                entry.Entity.UpdatedAt = now;
            }
        }
    }
}
