using System.Text.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.Security;
using Tiku.Domain.Commerce;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.PlatformAdmin;

internal sealed class PlatformAdminService(
    ICurrentAccessContext currentAccessContext,
    ITenantExecutionScope tenantExecutionScope) : IPlatformAdminService
{
    public async Task<PlatformOverview> GetOverviewAsync(
        PlatformAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformDashboardView, cancellationToken);
        return await ExecuteSystemAsync("platform overview", async dbContext =>
        {
            var row = await dbContext.Database.SqlQuery<PlatformOverviewRow>($"""
                SELECT
                    (SELECT count(*)::integer FROM tenants WHERE mode <> 'platform_owned') AS "TenantCount",
                    (SELECT count(*)::integer FROM tenants WHERE mode <> 'platform_owned' AND status = 'active') AS "ActiveTenantCount",
                    (SELECT count(*)::integer FROM tenants WHERE mode <> 'platform_owned' AND status = 'suspended') AS "SuspendedTenantCount",
                    (SELECT count(*)::integer FROM orders) AS "OrderCount",
                    (SELECT count(*)::integer FROM orders WHERE status = 'paid') AS "PaidOrderCount",
                    (SELECT COALESCE(sum(amount_cents - refunded_amount_cents), 0)::integer FROM orders WHERE status IN ('paid', 'partially_refunded')) AS "RevenueCents",
                    (SELECT count(*)::integer FROM question_banks) AS "QuestionBankCount",
                    (SELECT count(*)::integer FROM questions WHERE status = 'published') AS "QuestionCount",
                    (SELECT count(DISTINCT user_id)::integer FROM practice_sessions WHERE started_at >= {DateTimeOffset.UtcNow.AddDays(-7)}) AS "LearningActiveUserCount"
                """).SingleAsync(cancellationToken);
            return new PlatformOverview(
                row.TenantCount,
                row.ActiveTenantCount,
                row.SuspendedTenantCount,
                row.OrderCount,
                row.PaidOrderCount,
                row.RevenueCents,
                row.QuestionBankCount,
                row.QuestionCount,
                row.LearningActiveUserCount);
        }, cancellationToken);
    }

    private sealed class PlatformOverviewRow
    {
        public int TenantCount { get; init; }
        public int ActiveTenantCount { get; init; }
        public int SuspendedTenantCount { get; init; }
        public int OrderCount { get; init; }
        public int PaidOrderCount { get; init; }
        public int RevenueCents { get; init; }
        public int QuestionBankCount { get; init; }
        public int QuestionCount { get; init; }
        public int LearningActiveUserCount { get; init; }
    }

    public async Task<PlatformTenantList> GetTenantsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant list", async dbContext =>
        {
            var tenants = dbContext.Tenants.AsNoTracking()
                .Where(tenant => tenant.Mode != TenantMode.PlatformOwned);
            if (!string.IsNullOrWhiteSpace(query.Search))
            {
                var search = query.Search.Trim();
                tenants = tenants.Where(tenant =>
                    tenant.Slug.Contains(search) ||
                    tenant.Name.Contains(search) ||
                    (tenant.LegalName != null && tenant.LegalName.Contains(search)));
            }

            if (!string.IsNullOrWhiteSpace(query.Status))
            {
                tenants = tenants.Where(tenant => tenant.Status == ParseTenantStatus(query.Status));
            }

            var rows = await tenants
                .OrderByDescending(tenant => tenant.CreatedAt)
                .Take(Limit(query.Limit))
                .Select(tenant => new
                {
                    Tenant = tenant,
                    DomainCount = dbContext.TenantDomains.Count(domain => domain.TenantId == tenant.Id),
                    SubscriptionExpiresAt = dbContext.TenantSaasSubscriptions
                        .Where(subscription => subscription.TenantId == tenant.Id)
                        .OrderByDescending(subscription => subscription.CurrentPeriodEnd)
                        .Select(subscription => (DateTimeOffset?)subscription.CurrentPeriodEnd)
                        .FirstOrDefault()
                })
                .ToArrayAsync(cancellationToken);

            return new PlatformTenantList(rows.Select(row => ToTenantItem(row.Tenant, row.DomainCount, row.SubscriptionExpiresAt)).ToArray());
        }, cancellationToken);
    }

    public async Task<PlatformTenantDetail> GetTenantDetailAsync(
        PlatformAdminActor actor,
        Guid tenantId,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant detail", async dbContext =>
        {
            var tenant = await dbContext.Tenants.AsNoTracking()
                .SingleOrDefaultAsync(item => item.Id == tenantId && item.Mode != TenantMode.PlatformOwned, cancellationToken)
                ?? throw new PlatformAdminException("Tenant was not found.", "tenant_not_found");
            var domains = await dbContext.TenantDomains.AsNoTracking()
                .Where(domain => domain.TenantId == tenantId)
                .OrderByDescending(domain => domain.IsPrimary)
                .ThenBy(domain => domain.Host)
                .ToArrayAsync(cancellationToken);
            var subscriptions = await dbContext.TenantSaasSubscriptions.AsNoTracking()
                .Where(subscription => subscription.TenantId == tenantId)
                .OrderByDescending(subscription => subscription.CreatedAt)
                .ToArrayAsync(cancellationToken);
            var billingProfile = await dbContext.TenantBillingProfiles.AsNoTracking()
                .SingleOrDefaultAsync(profile => profile.TenantId == tenantId, cancellationToken);

            return new PlatformTenantDetail(
                ToTenantItem(tenant, domains.Length, subscriptions.FirstOrDefault()?.CurrentPeriodEnd),
                domains.Select(ToDomainItem).ToArray(),
                subscriptions.Select(ToSubscriptionItem).ToArray(),
                billingProfile is null ? null : ToBillingProfileItem(billingProfile));
        }, cancellationToken);
    }

    public async Task<PlatformTenantProvisioningResult> CreateTenantAsync(
        PlatformAdminActor actor,
        CreatePlatformTenantCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant create", async (provider, dbContext) =>
        {
            await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
            var slug = NormalizeCode(command.Slug);
            if (await dbContext.Tenants.AnyAsync(tenant => tenant.Slug == slug, cancellationToken))
            {
                throw new PlatformAdminException("Tenant slug already exists.", "tenant_slug_exists");
            }
            var ownerEmail = Normalize(command.OwnerEmail);
            var ownerPhone = Normalize(command.OwnerPhone);
            var ownerIdentifier = ownerEmail ?? ownerPhone;
            if (ownerIdentifier is null)
            {
                throw new PlatformAdminException("Owner email or phone is required.", "tenant_owner_identifier_required");
            }
            if (await dbContext.Users.AnyAsync(user =>
                    (ownerEmail != null && user.NormalizedEmail == ownerEmail.ToUpperInvariant()) ||
                    (ownerPhone != null && user.Phone == ownerPhone), cancellationToken))
            {
                throw new PlatformAdminException("Tenant owner already exists.", "tenant_owner_exists");
            }

            var tenant = new Tenant
            {
                Slug = slug,
                Name = command.Name.Trim(),
                LegalName = Normalize(command.LegalName),
                Status = command.Status,
                Mode = TenantMode.Saas,
                BillingStatus = command.BillingStatus,
                Metadata = JsonObjectOrDefault(command.Metadata)
            };
            dbContext.Tenants.Add(tenant);
            var owner = new User
            {
                Email = ownerEmail,
                NormalizedEmail = ownerEmail?.ToUpperInvariant(),
                UserName = ownerIdentifier,
                NormalizedUserName = ownerIdentifier.ToUpperInvariant(),
                Phone = ownerPhone,
                PhoneNumber = ownerPhone,
                Name = command.OwnerName.Trim(),
                PrimaryRole = "tenant_owner",
                Status = UserStatus.Active,
                ForcePasswordChange = true,
                EmailConfirmed = ownerEmail is not null,
                PhoneNumberConfirmed = ownerPhone is not null
            };
            var createOwner = await provider.GetRequiredService<UserManager<User>>()
                .CreateAsync(owner, command.TemporaryPassword);
            if (!createOwner.Succeeded)
            {
                throw new PlatformAdminException(
                    string.Join("; ", createOwner.Errors.Select(error => error.Description)),
                    "tenant_owner_password_invalid");
            }
            tenant.OwnerUserId = owner.Id;
            dbContext.TenantMemberships.Add(new TenantMembership
            {
                TenantId = tenant.Id,
                UserId = owner.Id,
                Role = TenantRole.TenantOwner,
                Status = MembershipStatus.Active
            });
            dbContext.TenantAuthPolicies.Add(new TenantAuthPolicy
            {
                TenantId = tenant.Id,
                AllowExternalStudentSelfRegistration = false
            });
            await EnsureTenantOwnerRoleAsync(dbContext, tenant.Id, owner.Id, cancellationToken);
            AddAudit(dbContext, actor, "platform.tenant.created", tenant.Id, new { tenant.Slug, tenant.Name, tenant.Status, tenant.BillingStatus });
            await dbContext.SaveChangesAsync(cancellationToken);
            await transaction.CommitAsync(cancellationToken);
            return new PlatformTenantProvisioningResult(
                ToTenantItem(tenant, 0, null),
                owner.Id,
                ownerIdentifier,
                true);
        }, cancellationToken);
    }

    public async Task<PlatformTenantItem> UpdateTenantStatusAsync(
        PlatformAdminActor actor,
        UpdatePlatformTenantStatusCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant status update", async (provider, dbContext) =>
        {
            var tenant = await dbContext.Tenants
                .SingleOrDefaultAsync(item => item.Id == command.TenantId && item.Mode != TenantMode.PlatformOwned, cancellationToken)
                ?? throw new PlatformAdminException("Tenant was not found.", "tenant_not_found");
            var fromStatus = tenant.Status;
            var fromBilling = tenant.BillingStatus;
            tenant.Status = command.Status;
            tenant.BillingStatus = command.BillingStatus;
            AddAudit(dbContext, actor, "platform.tenant.status_changed", tenant.Id, new
            {
                FromStatus = fromStatus,
                ToStatus = tenant.Status,
                FromBillingStatus = fromBilling,
                ToBillingStatus = tenant.BillingStatus,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            await provider.GetRequiredService<ITenantFeatureCacheInvalidator>()
                .InvalidateAsync(tenant.Id, cancellationToken);
            var domainCount = await dbContext.TenantDomains.CountAsync(domain => domain.TenantId == tenant.Id, cancellationToken);
            var expiresAt = await dbContext.TenantSaasSubscriptions
                .Where(subscription => subscription.TenantId == tenant.Id)
                .OrderByDescending(subscription => subscription.CurrentPeriodEnd)
                .Select(subscription => (DateTimeOffset?)subscription.CurrentPeriodEnd)
                .FirstOrDefaultAsync(cancellationToken);
            return ToTenantItem(tenant, domainCount, expiresAt);
        }, cancellationToken);
    }

    public async Task<TenantBillingProfileItem> UpsertTenantBillingProfileAsync(
        PlatformAdminActor actor,
        UpsertPlatformTenantBillingProfileCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant billing profile upsert", async dbContext =>
        {
            await RequireTenantAsync(dbContext, command.TenantId, cancellationToken);
            var profile = await dbContext.TenantBillingProfiles.SingleOrDefaultAsync(
                item => item.TenantId == command.TenantId,
                cancellationToken);
            if (profile is null)
            {
                profile = new TenantBillingProfile { TenantId = command.TenantId };
                dbContext.TenantBillingProfiles.Add(profile);
            }

            profile.BillingName = Normalize(command.BillingName);
            profile.TaxId = Normalize(command.TaxId);
            profile.ContactName = Normalize(command.ContactName);
            profile.ContactPhone = Normalize(command.ContactPhone);
            profile.ContactEmail = Normalize(command.ContactEmail);
            profile.BillingAddress = Normalize(command.BillingAddress);
            profile.InvoiceTitle = Normalize(command.InvoiceTitle);
            profile.InvoiceType = command.InvoiceType;
            profile.BankName = Normalize(command.BankName);
            profile.BankAccountMasked = MaskBankAccount(command.BankAccountMasked);
            profile.Metadata = JsonObjectOrDefault(command.Metadata);
            AddAudit(dbContext, actor, "platform.tenant.billing_profile.updated", command.TenantId, new { profile.BillingName, profile.InvoiceType });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToBillingProfileItem(profile);
        }, cancellationToken);
    }

    public async Task<PlatformDomainList> GetDomainsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform domain list", async dbContext =>
        {
            var domains = dbContext.TenantDomains.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Status))
            {
                domains = domains.Where(domain => domain.Status == ParseDomainStatus(query.Status));
            }

            if (!string.IsNullOrWhiteSpace(query.Search))
            {
                var search = query.Search.Trim();
                domains = domains.Where(domain => domain.Host.Contains(search));
            }

            return new PlatformDomainList(await domains
                .OrderByDescending(domain => domain.UpdatedAt)
                .Take(Limit(query.Limit))
                .Select(domain => ToDomainItem(domain))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformDomainRecheckResult> RecheckDomainAsync(
        PlatformAdminActor actor,
        Guid domainId,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform domain recheck", async dbContext =>
        {
            var domain = await dbContext.TenantDomains.SingleOrDefaultAsync(domain => domain.Id == domainId, cancellationToken)
                ?? throw new PlatformAdminException("Tenant domain was not found.", "domain_not_found");
            domain.Status = domain.Status == TenantDomainStatus.Disabled ? TenantDomainStatus.Disabled : TenantDomainStatus.Pending;
            domain.LastCheckedAt = DateTimeOffset.UtcNow;
            domain.LastFailureReason = null;
            dbContext.BackgroundJobs.Add(new BackgroundJob
            {
                TenantId = domain.TenantId,
                JobType = "tenant_domain_recheck",
                Payload = JsonSerializer.SerializeToElement(new { domain.Id, domain.Host }),
                MaxRetries = 3
            });
            AddAudit(dbContext, actor, "platform.tenant_domain.recheck_requested", domain.TenantId, new { domain.Id, domain.Host });
            await dbContext.SaveChangesAsync(cancellationToken);
            return new PlatformDomainRecheckResult(domain.Id, domain.Status, domain.LastCheckedAt.Value);
        }, cancellationToken);
    }

    public async Task<PlatformStaffList> GetStaffAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformStaffManage, cancellationToken);
        return await ExecuteSystemAsync("platform staff list", async dbContext =>
        {
            var roleRows = await (
                    from userRole in dbContext.PlatformBackendUserRoles.AsNoTracking()
                    join role in dbContext.PlatformBackendRoles.AsNoTracking() on userRole.RoleId equals role.Id
                    join user in dbContext.Users.AsNoTracking() on userRole.UserId equals user.Id
                    select new { user, role.Code })
                .ToArrayAsync(cancellationToken);
            var items = roleRows
                .GroupBy(row => row.user.Id)
                .Select(group => ToStaffItem(group.First().user, group.Select(row => row.Code).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray()))
                .OrderBy(item => item.Email ?? item.PhoneMasked ?? item.UserId.ToString())
                .Take(Limit(query.Limit))
                .ToArray();
            return new PlatformStaffList(items);
        }, cancellationToken);
    }

    public async Task<PlatformStaffItem> UpsertStaffAsync(
        PlatformAdminActor actor,
        UpsertPlatformStaffCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformStaffManage, cancellationToken);
        return await ExecuteSystemAsync("platform staff upsert", async dbContext =>
        {
            var user = command.UserId.HasValue
                ? await dbContext.Users.SingleOrDefaultAsync(item => item.Id == command.UserId.Value, cancellationToken)
                : await dbContext.Users.SingleOrDefaultAsync(item =>
                    (!string.IsNullOrWhiteSpace(command.Email) && item.Email == command.Email) ||
                    (!string.IsNullOrWhiteSpace(command.Phone) && item.Phone == command.Phone),
                    cancellationToken);
            if (user is null)
            {
                user = new User
                {
                    Email = Normalize(command.Email),
                    NormalizedEmail = Normalize(command.Email)?.ToUpperInvariant(),
                    UserName = Normalize(command.Email) ?? Normalize(command.Phone),
                    NormalizedUserName = (Normalize(command.Email) ?? Normalize(command.Phone))?.ToUpperInvariant(),
                    Phone = Normalize(command.Phone),
                    PhoneNumber = Normalize(command.Phone),
                    Name = Normalize(command.Name),
                    PrimaryRole = "platform_admin",
                    Status = command.Status,
                    ForcePasswordChange = true
                };
                dbContext.Users.Add(user);
            }
            else
            {
                user.Email = Normalize(command.Email) ?? user.Email;
                user.NormalizedEmail = user.Email?.ToUpperInvariant();
                user.Phone = Normalize(command.Phone) ?? user.Phone;
                user.PhoneNumber = user.Phone;
                user.Name = Normalize(command.Name) ?? user.Name;
                user.Status = command.Status;
                user.PrimaryRole = "platform_admin";
            }

            var roleIds = command.RoleIds.Distinct().ToArray();
            var roleCount = await dbContext.PlatformBackendRoles.CountAsync(role => roleIds.Contains(role.Id), cancellationToken);
            if (roleCount != roleIds.Length)
            {
                throw new PlatformAdminException("One or more platform roles were not found.", "role_not_found");
            }

            await dbContext.SaveChangesAsync(cancellationToken);
            await dbContext.PlatformBackendUserRoles.Where(binding => binding.UserId == user.Id).ExecuteDeleteAsync(cancellationToken);
            dbContext.PlatformBackendUserRoles.AddRange(roleIds.Select(roleId => new PlatformBackendUserRole
            {
                UserId = user.Id,
                RoleId = roleId
            }));
            AddAudit(dbContext, actor, "platform.staff.upserted", user.Id, new { user.Email, Phone = MaskPhone(user.Phone), user.Status, RoleIds = roleIds });
            await dbContext.SaveChangesAsync(cancellationToken);
            var roleCodes = await dbContext.PlatformBackendRoles.AsNoTracking()
                .Where(role => roleIds.Contains(role.Id))
                .Select(role => role.Code)
                .ToArrayAsync(cancellationToken);
            return ToStaffItem(user, roleCodes);
        }, cancellationToken);
    }

    public async Task<PlatformStaffItem> UpdateStaffStatusAsync(
        PlatformAdminActor actor,
        UpdatePlatformStaffStatusCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformStaffManage, cancellationToken);
        return await ExecuteSystemAsync("platform staff status update", async dbContext =>
        {
            var user = await dbContext.Users.SingleOrDefaultAsync(item => item.Id == command.UserId, cancellationToken)
                ?? throw new PlatformAdminException("Platform staff user was not found.", "staff_not_found");
            var from = user.Status;
            user.Status = command.Status;
            AddAudit(dbContext, actor, "platform.staff.status_changed", user.Id, new { From = from, To = command.Status, command.Reason });
            await dbContext.SaveChangesAsync(cancellationToken);
            var roleCodes = await (
                    from binding in dbContext.PlatformBackendUserRoles.AsNoTracking()
                    join role in dbContext.PlatformBackendRoles.AsNoTracking() on binding.RoleId equals role.Id
                    where binding.UserId == user.Id
                    select role.Code)
                .ToArrayAsync(cancellationToken);
            return ToStaffItem(user, roleCodes);
        }, cancellationToken);
    }

    public async Task<PlatformAuditLogList> GetAuditLogsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformAuditView, cancellationToken);
        return await ExecuteSystemAsync("platform audit log list", async dbContext =>
        {
            var logs = dbContext.AuditLogs.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Search))
            {
                var search = query.Search.Trim();
                logs = logs.Where(log => log.Action.Contains(search) || (log.TargetType != null && log.TargetType.Contains(search)));
            }

            return new PlatformAuditLogList(await logs
                .OrderByDescending(log => log.CreatedAt)
                .Take(Limit(query.Limit))
                .Select(log => new PlatformAuditLogItem(
                    log.Id,
                    log.TenantId,
                    log.ActorUserId,
                    log.Action,
                    log.TargetType,
                    log.TargetId,
                    log.Details,
                    log.IpAddress,
                    log.UserAgent,
                    log.CreatedAt))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformAuditAlertList> GetAuditAlertsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformAuditView, cancellationToken);
        return await ExecuteSystemAsync("platform audit alert list", async dbContext =>
        {
            var alerts = dbContext.PlatformAuditAlerts.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Status))
            {
                alerts = alerts.Where(alert => alert.Status == ParseAuditAlertStatus(query.Status));
            }

            return new PlatformAuditAlertList(await alerts
                .OrderByDescending(alert => alert.LastSeenAt)
                .Take(Limit(query.Limit))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformAuditAlert> UpdateAuditAlertStatusAsync(
        PlatformAdminActor actor,
        UpdatePlatformAuditAlertStatusCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformAuditView, cancellationToken);
        return await ExecuteSystemAsync("platform audit alert status update", async dbContext =>
        {
            var alert = await dbContext.PlatformAuditAlerts.SingleOrDefaultAsync(item => item.Id == command.AlertId, cancellationToken)
                ?? throw new PlatformAdminException("Platform audit alert was not found.", "audit_alert_not_found");
            alert.Status = command.Status;
            alert.ResolutionNote = Normalize(command.ResolutionNote);
            if (command.Status == PlatformAuditAlertStatus.Acknowledged)
            {
                alert.AcknowledgedBy = actor.UserId;
                alert.AcknowledgedAt ??= DateTimeOffset.UtcNow;
            }
            else if (command.Status is PlatformAuditAlertStatus.Resolved or PlatformAuditAlertStatus.Ignored)
            {
                alert.ResolvedBy = actor.UserId;
                alert.ResolvedAt ??= DateTimeOffset.UtcNow;
            }

            AddAudit(dbContext, actor, "platform.audit_alert.status_changed", alert.Id, new { alert.Status, command.ResolutionNote });
            await dbContext.SaveChangesAsync(cancellationToken);
            return alert;
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningChannelList> GetBillingDunningChannelsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning channel list", async dbContext =>
        {
            var channels = dbContext.PlatformBillingDunningNotificationChannels.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Search))
            {
                var search = query.Search.Trim();
                channels = channels.Where(channel =>
                    channel.ChannelCode.Contains(search) ||
                    channel.Name.Contains(search));
            }

            if (!string.IsNullOrWhiteSpace(query.Status))
            {
                var enabled = ParseEnabledStatus(query.Status);
                channels = channels.Where(channel => channel.Enabled == enabled);
            }

            return new PlatformBillingDunningChannelList(await channels
                .OrderByDescending(channel => channel.Enabled)
                .ThenBy(channel => channel.MinReminderLevel)
                .ThenBy(channel => channel.ChannelCode)
                .Take(Limit(query.Limit))
                .Select(channel => ToDunningChannelItem(channel))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningChannelItem> UpsertBillingDunningChannelAsync(
        PlatformAdminActor actor,
        UpsertPlatformBillingDunningChannelCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning channel upsert", async dbContext =>
        {
            var code = NormalizeCode(command.ChannelCode);
            var channel = command.ChannelId.HasValue
                ? await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(item => item.Id == command.ChannelId.Value, cancellationToken)
                : await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(item => item.ChannelCode == code, cancellationToken);
            if (channel is null)
            {
                channel = new PlatformBillingDunningNotificationChannel { ChannelCode = code };
                dbContext.PlatformBillingDunningNotificationChannels.Add(channel);
            }

            channel.ChannelCode = code;
            channel.Name = command.Name.Trim();
            channel.Description = Normalize(command.Description);
            channel.Enabled = command.Enabled;
            channel.Provider = command.Provider;
            channel.WebhookUrl = command.WebhookUrl.Trim();
            channel.SecretRef = Normalize(command.SecretRef);
            channel.ReminderTypes = NormalizeArray(command.ReminderTypes, ["overdue", "final_notice"]);
            channel.ReminderChannels = NormalizeArray(command.ReminderChannels, ["internal"]);
            channel.MinReminderLevel = Math.Clamp(command.MinReminderLevel, 1, 20);
            channel.TenantIds = command.TenantIds.Where(id => id != Guid.Empty).Distinct().ToArray();
            channel.TimeoutSeconds = Math.Clamp(command.TimeoutSeconds, 1, 60);
            channel.Metadata = JsonObjectOrDefault(command.Metadata);
            AddAudit(dbContext, actor, "platform.billing_dunning_channel.upserted", channel.Id, new
            {
                channel.ChannelCode,
                channel.Name,
                channel.Enabled,
                channel.Provider,
                Webhook = MaskWebhook(channel.WebhookUrl),
                channel.SecretRef
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningChannelItem(channel);
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningChannelItem> DisableBillingDunningChannelAsync(
        PlatformAdminActor actor,
        DisablePlatformBillingDunningChannelCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning channel disable", async dbContext =>
        {
            var channel = await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(item => item.Id == command.ChannelId, cancellationToken)
                ?? throw new PlatformAdminException("Platform dunning channel was not found.", "dunning_channel_not_found");
            channel.Enabled = false;
            AddAudit(dbContext, actor, "platform.billing_dunning_channel.disabled", channel.Id, new
            {
                channel.ChannelCode,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningChannelItem(channel);
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningEventList> GetBillingDunningEventsAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event list", async dbContext =>
        {
            var events = dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking();
            if (!string.IsNullOrWhiteSpace(query.Status))
            {
                events = events.Where(item => item.Status == ParseDunningEventStatus(query.Status));
            }

            return new PlatformBillingDunningEventList(await events
                .OrderByDescending(item => item.CreatedAt)
                .Take(Limit(query.Limit))
                .Select(item => ToDunningEventItem(item))
                .ToArrayAsync(cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningEventItem> GetBillingDunningEventDetailAsync(
        PlatformAdminActor actor,
        Guid eventId,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event detail", async dbContext =>
        {
            var item = await dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking()
                .SingleOrDefaultAsync(item => item.Id == eventId, cancellationToken)
                ?? throw new PlatformAdminException("Platform dunning event was not found.", "dunning_event_not_found");
            return ToDunningEventItem(item);
        }, cancellationToken);
    }

    public async Task<PlatformBillingDunningEventItem> RetryBillingDunningEventAsync(
        PlatformAdminActor actor,
        RetryPlatformBillingDunningEventCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken);
        return await ExecuteSystemAsync("platform dunning event retry", async dbContext =>
        {
            var item = await dbContext.PlatformBillingDunningNotificationEvents.SingleOrDefaultAsync(item => item.Id == command.EventId, cancellationToken)
                ?? throw new PlatformAdminException("Platform dunning event was not found.", "dunning_event_not_found");
            item.Status = PlatformBillingDunningNotificationStatus.Pending;
            item.NextAttemptAt = DateTimeOffset.UtcNow;
            item.LastError = null;
            AddAudit(dbContext, actor, "platform.billing_dunning_event.retry_requested", item.Id, new
            {
                item.TenantId,
                item.ChannelId,
                item.ReminderId,
                item.InvoiceId,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDunningEventItem(item);
        }, cancellationToken);
    }

    private async Task AssertPlatformPermissionAsync(
        PlatformAdminActor actor,
        string permissionCode,
        CancellationToken cancellationToken)
    {
        var access = await currentAccessContext.GetAsync(cancellationToken);
        if (access.UserId != actor.UserId || !access.HasPlatformPermission(permissionCode))
        {
            throw new PlatformAdminException("Platform admin access is required.", "platform_access_denied");
        }
    }

    private Task<TResult> ExecuteSystemAsync<TResult>(
        string reason,
        Func<TikuDbContext, Task<TResult>> operation,
        CancellationToken cancellationToken)
    {
        return ExecuteSystemAsync(reason, (_, dbContext) => operation(dbContext), cancellationToken);
    }

    private Task<TResult> ExecuteSystemAsync<TResult>(
        string reason,
        Func<IServiceProvider, TikuDbContext, Task<TResult>> operation,
        CancellationToken cancellationToken)
    {
        return tenantExecutionScope.ExecuteAsync(
            new SystemScopeRequest(
                null,
                SystemScopeCallerType.Platform,
                nameof(PlatformAdminService),
                reason,
                Guid.NewGuid().ToString("N"),
                IsGlobal: true),
            async (provider, _) => await operation(provider, provider.GetRequiredService<TikuDbContext>()),
            cancellationToken);
    }

    private static async Task RequireTenantAsync(TikuDbContext dbContext, Guid tenantId, CancellationToken cancellationToken)
    {
        var exists = await dbContext.Tenants.AnyAsync(tenant => tenant.Id == tenantId && tenant.Mode != TenantMode.PlatformOwned, cancellationToken);
        if (!exists)
        {
            throw new PlatformAdminException("Tenant was not found.", "tenant_not_found");
        }
    }

    private static void AddAudit(TikuDbContext dbContext, PlatformAdminActor actor, string action, Guid targetId, object details)
    {
        dbContext.AuditLogs.Add(new AuditLog
        {
            ActorUserId = actor.UserId,
            Action = action,
            TargetType = action.Split('.')[1],
            TargetId = targetId.ToString("N"),
            Details = JsonSerializer.SerializeToElement(details)
        });
    }

    private static PlatformTenantItem ToTenantItem(Tenant tenant, int domainCount, DateTimeOffset? subscriptionExpiresAt) =>
        new(
            tenant.Id,
            tenant.Slug,
            tenant.Name,
            tenant.LegalName,
            tenant.Status,
            tenant.Mode,
            tenant.BillingStatus,
            subscriptionExpiresAt,
            domainCount,
            tenant.CreatedAt,
            tenant.UpdatedAt);

    private static PlatformTenantDomainItem ToDomainItem(TenantDomain domain) =>
        new(
            domain.Id,
            domain.TenantId,
            domain.Host,
            domain.DomainType,
            domain.Status,
            domain.IsPrimary,
            domain.VerifiedAt,
            domain.DnsVerifiedAt,
            domain.TlsReadyAt,
            domain.LastCheckedAt,
            domain.LastFailureReason);

    private static PlatformTenantSubscriptionItem ToSubscriptionItem(TenantSaasSubscription subscription) =>
        new(
            subscription.Id,
            subscription.TenantId,
            subscription.BaseOfferingVersionId,
            subscription.Status,
            subscription.StartsAt,
            subscription.CurrentPeriodStart,
            subscription.CurrentPeriodEnd,
            subscription.CancelAtPeriodEnd,
            subscription.Metadata);

    private static TenantBillingProfileItem ToBillingProfileItem(TenantBillingProfile profile) =>
        new(
            profile.TenantId,
            profile.BillingName,
            profile.TaxId,
            profile.ContactName,
            MaskPhone(profile.ContactPhone),
            profile.ContactEmail,
            profile.BillingAddress,
            profile.InvoiceTitle,
            profile.InvoiceType,
            profile.BankName,
            profile.BankAccountMasked,
            profile.Metadata);

    private static PlatformBillingDunningChannelItem ToDunningChannelItem(PlatformBillingDunningNotificationChannel channel) =>
        new(
            channel.Id,
            channel.ChannelCode,
            channel.Name,
            channel.Description,
            channel.Enabled,
            channel.Provider,
            MaskWebhook(channel.WebhookUrl),
            channel.SecretRef,
            channel.ReminderTypes,
            channel.ReminderChannels,
            channel.MinReminderLevel,
            channel.TenantIds,
            channel.TimeoutSeconds,
            channel.Metadata,
            channel.CreatedAt,
            channel.UpdatedAt);

    private static PlatformBillingDunningEventItem ToDunningEventItem(PlatformBillingDunningNotificationEvent item) =>
        new(
            item.Id,
            item.TenantId,
            item.ChannelId,
            item.ReminderId,
            item.InvoiceId,
            item.Provider,
            item.Status,
            item.Attempts,
            item.ScheduledAt,
            item.NextAttemptAt,
            item.LastAttemptAt,
            item.SentAt,
            item.LastError,
            item.LastHttpCode,
            item.LastResponseSummary,
            item.RequestPayload,
            item.Metadata,
            item.CreatedAt,
            item.UpdatedAt);

    private static PlatformStaffItem ToStaffItem(User user, IReadOnlyCollection<string> roleCodes) =>
        new(
            user.Id,
            user.Name,
            MaskPhone(user.Phone),
            user.Email,
            user.Status,
            roleCodes,
            user.CreatedAt,
            user.UpdatedAt);

    private static int Limit(int? limit) => Math.Clamp(limit ?? 50, 1, 200);

    private static string NormalizeCode(string value) => value.Trim().ToLowerInvariant();
    private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim();

    private static JsonElement JsonObjectOrDefault(JsonElement value) =>
        value.ValueKind == JsonValueKind.Object ? value.Clone() : JsonDocument.Parse("{}").RootElement.Clone();

    private static string? MaskPhone(string? phone)
    {
        var value = Normalize(phone);
        return value is { Length: >= 7 }
            ? $"{value[..3]}****{value[^4..]}"
            : value;
    }

    private static string? MaskBankAccount(string? account)
    {
        var value = Normalize(account);
        return value is { Length: > 8 }
            ? $"****{value[^4..]}"
            : value;
    }

    private static string MaskWebhook(string webhookUrl)
    {
        var value = Normalize(webhookUrl) ?? string.Empty;
        if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
        {
            return value.Length <= 16 ? "****" : $"{value[..8]}****{value[^4..]}";
        }

        return $"{uri.Scheme}://{uri.Host}/****";
    }

    private static string[] NormalizeArray(IReadOnlyCollection<string> values, string[] fallback)
    {
        var normalized = values
            .Select(Normalize)
            .Where(value => value is not null)
            .Select(value => value!)
            .Distinct(StringComparer.Ordinal)
            .ToArray();
        return normalized.Length == 0 ? fallback : normalized;
    }

    private static bool ParseEnabledStatus(string status)
    {
        return NormalizeCode(status) switch
        {
            "enabled" or "active" or "true" => true,
            "disabled" or "inactive" or "false" => false,
            _ => throw InvalidStatus(status)
        };
    }

    private static TenantStatus ParseTenantStatus(string status) =>
        Enum.TryParse<TenantStatus>(status, true, out var value) ? value : throw InvalidStatus(status);

    private static TenantDomainStatus ParseDomainStatus(string status) =>
        Enum.TryParse<TenantDomainStatus>(status, true, out var value) ? value : throw InvalidStatus(status);

    private static async Task EnsureTenantOwnerRoleAsync(
        TikuDbContext dbContext,
        Guid tenantId,
        Guid ownerUserId,
        CancellationToken cancellationToken)
    {
        var existingFeatureCodes = await dbContext.SaasFeatures
            .Where(value => SaasFeatureCatalog.All.Contains(value.Code))
            .Select(value => value.Code)
            .ToArrayAsync(cancellationToken);
        dbContext.SaasFeatures.AddRange(SaasFeatureCatalog.All
            .Except(existingFeatureCodes, StringComparer.Ordinal)
            .Select((code, index) => new SaasFeature
            {
                Code = code,
                Name = code,
                Category = code.Split('.')[0],
                IsCore = code == SaasFeatureCatalog.CoreBackoffice,
                Status = SaasFeatureStatus.Active,
                SortOrder = index * 10
            }));

        var moduleCodes = BackendPermissions.Tenant
            .Select(PermissionModuleCatalog.ResolvePermissionModuleCode)
            .Distinct(StringComparer.Ordinal)
            .ToArray();
        var existingModuleCodes = await dbContext.PermissionModules
            .Where(value => moduleCodes.Contains(value.Code))
            .Select(value => value.Code)
            .ToArrayAsync(cancellationToken);
        dbContext.PermissionModules.AddRange(moduleCodes
            .Except(existingModuleCodes, StringComparer.Ordinal)
            .Select(code => new PermissionModule
            {
                Code = code,
                Name = code,
                Area = BackendPermissionArea.Tenant,
                RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code]
            }));

        var existingPermissionCodes = await dbContext.BackendPermissions
            .Where(value => BackendPermissions.Tenant.Contains(value.Code))
            .Select(value => value.Code)
            .ToArrayAsync(cancellationToken);
        dbContext.BackendPermissions.AddRange(BackendPermissions.Tenant
            .Except(existingPermissionCodes, StringComparer.Ordinal)
            .Select(code => new BackendPermission
            {
                Code = code,
                Name = code,
                Area = BackendPermissionArea.Tenant,
                PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code),
                IsSystem = true
            }));

        var role = new TenantBackendRole
        {
            TenantId = tenantId,
            Code = "tenant_owner",
            Name = "租户所有者",
            Status = BackendRoleStatus.Active,
            IsSystem = true,
            Description = "系统内置租户所有者角色",
            DataScope = JsonSerializer.SerializeToElement(new { mode = "all" })
        };
        dbContext.TenantBackendRoles.Add(role);
        dbContext.TenantBackendRolePermissions.AddRange(BackendPermissions.Tenant.Select(code => new TenantBackendRolePermission
        {
            TenantId = tenantId,
            RoleId = role.Id,
            PermissionCode = code
        }));
        dbContext.TenantBackendUserRoles.Add(new TenantBackendUserRole
        {
            TenantId = tenantId,
            UserId = ownerUserId,
            RoleId = role.Id
        });
    }

    private static PlatformAuditAlertStatus ParseAuditAlertStatus(string status) =>
        Enum.TryParse<PlatformAuditAlertStatus>(status, true, out var value) ? value : throw InvalidStatus(status);

    private static PlatformBillingDunningNotificationStatus ParseDunningEventStatus(string status) =>
        Enum.TryParse<PlatformBillingDunningNotificationStatus>(status, true, out var value) ? value : throw InvalidStatus(status);

    private static PlatformAdminException InvalidStatus(string status) =>
        new($"Unsupported status '{status}'.", "invalid_status");
}
