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

namespace Tiku.Infrastructure.PlatformAdmin;

internal sealed partial class PlatformAdminService
{
    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);
            var billingPolicy = await dbContext.TenantBillingPolicies.AsNoTracking()
                .SingleOrDefaultAsync(policy => policy.TenantId == tenantId, cancellationToken);

            return new PlatformTenantDetail(
                ToTenantItem(tenant, domains.Length, subscriptions.FirstOrDefault()?.CurrentPeriodEnd),
                domains.Select(ToDomainItemWithInstructions).ToArray(),
                subscriptions.Select(ToSubscriptionItem).ToArray(),
                billingProfile is null ? null : ToBillingProfileItem(billingProfile),
                billingPolicy is null ? null : ToBillingPolicyItem(billingPolicy),
                await OwnerActivationStatusAsync(dbContext, tenant, cancellationToken));
        }, cancellationToken);
    }

    public async Task<PlatformTenantProvisioningResult> CreateTenantAsync(
        PlatformAdminActor actor,
        CreatePlatformTenantCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        var idempotencyKey = Required(command.IdempotencyKey, "Idempotency-Key");
        var requestHash = ProvisioningRequestHash(command);
        try
        {
            return await ExecuteSystemAsync("platform tenant create", async (provider, dbContext) =>
            {
                var existingRequest = await dbContext.PlatformOperationIdempotencies.AsNoTracking()
                    .SingleOrDefaultAsync(value =>
                            value.ActorUserId == actor.UserId &&
                            value.Scope == "platform.tenant.create" &&
                            value.IdempotencyKey == idempotencyKey,
                        cancellationToken);
                if (existingRequest is not null)
                {
                    if (!string.Equals(existingRequest.RequestHash, requestHash, StringComparison.Ordinal))
                        throw new PlatformAdminException(
                            "Idempotency key was already used with a different request.",
                            "idempotency_conflict");
                    return await ProvisioningReplayResultAsync(dbContext, existingRequest.ResourceId,
                        cancellationToken);
                }

                var tenantId = Guid.NewGuid();
                dbContext.PlatformOperationIdempotencies.Add(new PlatformOperationIdempotency
                {
                    ActorUserId = actor.UserId,
                    Scope = "platform.tenant.create",
                    IdempotencyKey = idempotencyKey,
                    RequestHash = requestHash,
                    ResourceId = tenantId
                });
                await dbContext.SaveChangesAsync(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");

                SaasOfferingVersion? initialVersion = null;
                if (command.InitialOfferingVersionId.HasValue)
                {
                    initialVersion = await dbContext.SaasOfferingVersions.AsNoTracking()
                                         .SingleOrDefaultAsync(value =>
                                                 value.Id == command.InitialOfferingVersionId &&
                                                 value.Status == SaasOfferingVersionStatus.Published,
                                             cancellationToken)
                                     ?? throw new PlatformAdminException(
                                         "Initial offering version was not found or published.",
                                         "saas_offering_version_not_found");
                    var offeringType = await dbContext.SaasOfferings.AsNoTracking()
                        .Where(value => value.Id == initialVersion.OfferingId)
                        .Select(value => value.Type)
                        .SingleAsync(cancellationToken);
                    if (offeringType != SaasOfferingType.BasePlan)
                        throw new PlatformAdminException("Initial offering must be a base plan.",
                            "saas_base_offering_required");
                }
                else
                {
                    var now = DateTimeOffset.UtcNow;
                    initialVersion = await (
                                         from offering in dbContext.SaasOfferings.AsNoTracking()
                                         join version in dbContext.SaasOfferingVersions.AsNoTracking() on offering.Id
                                             equals version.OfferingId
                                         where offering.Code == NormalizeCode(provisioning.DefaultBaseOfferingCode) &&
                                               offering.Type == SaasOfferingType.BasePlan &&
                                               offering.Status == SaasOfferingStatus.Active &&
                                               version.Status == SaasOfferingVersionStatus.Published &&
                                               (version.EffectiveAt == null || version.EffectiveAt <= now)
                                         orderby version.Version descending
                                         select version).FirstOrDefaultAsync(cancellationToken)
                                     ?? throw new PlatformAdminException(
                                         "The default base offering does not have an effective published version.",
                                         "default_offering_unavailable");
                }

                var tenant = new Tenant
                {
                    Id = tenantId,
                    Slug = slug,
                    Name = command.Name.Trim(),
                    LegalName = Normalize(command.LegalName),
                    Status = command.Status,
                    Mode = TenantMode.Saas,
                    BillingStatus = initialVersion is null ? command.BillingStatus : BillingStatus.Trial,
                    Metadata = JsonObjectOrDefault(command.Metadata)
                };
                dbContext.Tenants.Add(tenant);
                dbContext.AuthorizationScopeVersions.Add(new AuthorizationScopeVersion
                {
                    Realm = AuthRealm.Tenant,
                    TenantId = tenant.Id
                });
                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 userManager = provider.GetRequiredService<UserManager<User>>();
                var createOwner = await userManager.CreateAsync(owner);
                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
                });
                var primaryDomain = CreatePrimaryDomain(tenant.Id, command.PrimaryDomainHost);
                if (await dbContext.TenantDomains.AnyAsync(value => value.Host == primaryDomain.Host,
                        cancellationToken))
                    throw new PlatformAdminException("Primary domain is already assigned.", "tenant_domain_exists");
                dbContext.TenantDomains.Add(primaryDomain);
                dbContext.TenantFrontendConfigs.Add(TenantFrontendConfigDefaults.Create(tenant.Id, tenant.Name));
                await EnsureTenantOwnerRoleAsync(dbContext, tenant.Id, owner.Id, cancellationToken);
                var policy = new TenantBillingPolicy
                {
                    TenantId = tenant.Id,
                    CollectionMode = command.CollectionMode,
                    DefaultPaymentProvider = NormalizeCode(command.DefaultPaymentProvider),
                    AutoGenerateRenewal = command.AutoGenerateRenewal,
                    RenewalLeadDays = Math.Clamp(command.RenewalLeadDays, 1, 90)
                };
                dbContext.TenantBillingPolicies.Add(policy);

                DateTimeOffset? subscriptionExpiresAt = null;
                {
                    var now = DateTimeOffset.UtcNow;
                    var trialDays = command.TrialDays ?? provisioning.DefaultTrialDays;
                    subscriptionExpiresAt = now.AddDays(Math.Clamp(trialDays, 1, 365));
                    var subscription = new TenantSaasSubscription
                    {
                        TenantId = tenant.Id,
                        BaseOfferingVersionId = initialVersion!.Id,
                        Status = TenantSaasSubscriptionStatus.Trial,
                        StartsAt = now,
                        CurrentPeriodStart = now,
                        CurrentPeriodEnd = subscriptionExpiresAt.Value,
                        LifecycleVersion = 1
                    };
                    dbContext.TenantSaasSubscriptions.Add(subscription);
                    dbContext.TenantSaasSubscriptionItems.Add(new TenantSaasSubscriptionItem
                    {
                        TenantId = tenant.Id,
                        SubscriptionId = subscription.Id,
                        OfferingVersionId = initialVersion.Id,
                        ItemType = TenantSaasSubscriptionItemType.BasePlan,
                        Status = TenantSaasSubscriptionItemStatus.Active,
                        StartsAt = now,
                        EndsAt = subscriptionExpiresAt.Value
                    });
                }
                AddAudit(dbContext, actor, "platform.tenant.created", tenant.Id,
                    new { tenant.Slug, tenant.Name, tenant.Status, tenant.BillingStatus });
                await dbContext.SaveChangesAsync(cancellationToken);
                return new PlatformTenantProvisioningResult(
                    ToTenantItem(tenant, 1, subscriptionExpiresAt),
                    owner.Id,
                    ownerIdentifier,
                    owner.ForcePasswordChange,
                    ToDomainItemWithInstructions(primaryDomain),
                    new PlatformOwnerActivationStatus("domain_pending", null, null),
                    false);
            }, cancellationToken);
        }
        catch (DbUpdateException exception) when (IsPlatformOperationIdempotencyConflict(exception))
        {
            return await ExecuteSystemAsync("platform tenant create idempotency replay", async dbContext =>
            {
                var existingRequest = await dbContext.PlatformOperationIdempotencies.AsNoTracking()
                    .SingleAsync(value => value.ActorUserId == actor.UserId &&
                                          value.Scope == "platform.tenant.create" &&
                                          value.IdempotencyKey == idempotencyKey,
                        cancellationToken);
                if (!string.Equals(existingRequest.RequestHash, requestHash, StringComparison.Ordinal))
                    throw new PlatformAdminException(
                        "Idempotency key was already used with a different request.",
                        "idempotency_conflict");
                return await ProvisioningReplayResultAsync(dbContext, existingRequest.ResourceId, cancellationToken);
            }, cancellationToken);
        }
    }

    public async Task<PlatformTenantDomainItem> ReplacePrimaryDomainAsync(
        PlatformAdminActor actor,
        ReplacePlatformPrimaryDomainCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        if (string.IsNullOrWhiteSpace(command.Reason))
            throw new PlatformAdminException("Primary domain replacement reason is required.",
                "domain_change_reason_required");

        return await ExecuteSystemAsync("platform primary domain replace", async dbContext =>
        {
            var tenant = await dbContext.Tenants.SingleOrDefaultAsync(value =>
                             value.Id == command.TenantId && value.Mode != TenantMode.PlatformOwned, cancellationToken)
                         ?? throw new PlatformAdminException("Tenant was not found.", "tenant_not_found");
            var existing = await dbContext.TenantDomains
                .Where(value => value.TenantId == tenant.Id && value.IsPrimary)
                .ToArrayAsync(cancellationToken);
            foreach (var domain in existing)
            {
                domain.IsPrimary = false;
                domain.Status = TenantDomainStatus.Disabled;
            }

            var now = DateTimeOffset.UtcNow;
            await dbContext.TenantOwnerActivationGrants
                .Where(value => value.TenantId == tenant.Id && value.ConsumedAt == null && value.RevokedAt == null)
                .ExecuteUpdateAsync(setters => setters
                        .SetProperty(value => value.RevokedAt, now)
                        .SetProperty(value => value.RevokedBy, actor.UserId)
                        .SetProperty(value => value.RevocationReason, "Primary domain replaced: " + command.Reason),
                    cancellationToken);
            var next = CreatePrimaryDomain(tenant.Id, command.Host);
            if (await dbContext.TenantDomains.AnyAsync(value => value.Host == next.Host, cancellationToken))
                throw new PlatformAdminException("Primary domain is already assigned.", "tenant_domain_exists");
            dbContext.TenantDomains.Add(next);
            AddAudit(dbContext, actor, "platform.tenant_primary_domain.replaced", tenant.Id,
                new { next.Id, next.Host, command.Reason });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToDomainItemWithInstructions(next);
        }, cancellationToken);
    }

    public async Task<PlatformOwnerActivationLinkResult> IssueOwnerActivationLinkAsync(
        PlatformAdminActor actor,
        IssuePlatformOwnerActivationLinkCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        var idempotencyKey = Required(command.IdempotencyKey, "Idempotency-Key");
        if (string.IsNullOrWhiteSpace(command.Reason))
            throw new PlatformAdminException("Owner activation issuance reason is required.",
                "owner_activation_reason_required");

        return await ExecuteSystemAsync("platform owner activation link issue", async dbContext =>
        {
            var lockKey = $"owner-activation:{command.TenantId:N}";
            await dbContext.Database.ExecuteSqlInterpolatedAsync(
                $"select pg_advisory_xact_lock(hashtextextended({lockKey}, 0))",
                cancellationToken);
            var existingRequest = await dbContext.PlatformOperationIdempotencies.AsNoTracking()
                .SingleOrDefaultAsync(value => value.ActorUserId == actor.UserId &&
                                               value.Scope == "platform.tenant.owner_activation.issue" &&
                                               value.IdempotencyKey == idempotencyKey, cancellationToken);
            var requestHash = OwnerActivationRequestHash(command);
            if (existingRequest is not null)
            {
                if (!string.Equals(existingRequest.RequestHash, requestHash, StringComparison.Ordinal))
                    throw new PlatformAdminException(
                        "Idempotency key was already used with a different request.", "idempotency_conflict");
                return await OwnerActivationReplayResultAsync(dbContext, existingRequest.ResourceId, cancellationToken);
            }

            var tenant = await dbContext.Tenants.SingleOrDefaultAsync(value =>
                             value.Id == command.TenantId && value.Status == TenantStatus.Active &&
                             value.Mode != TenantMode.PlatformOwned, cancellationToken)
                         ?? throw new PlatformAdminException("An active tenant was not found.", "tenant_not_active");
            var ownerId = tenant.OwnerUserId
                          ?? throw new PlatformAdminException("Tenant owner was not found.", "tenant_owner_not_found");
            var owner = await dbContext.Users.SingleAsync(value => value.Id == ownerId, cancellationToken);
            if (owner.PasswordHash is not null || !owner.ForcePasswordChange)
                throw new PlatformAdminException("Tenant owner is already activated.", "owner_already_activated");

            var primaryDomain = await dbContext.TenantDomains.SingleOrDefaultAsync(value =>
                                        value.TenantId == tenant.Id && value.IsPrimary &&
                                        value.Status == TenantDomainStatus.Active,
                                    cancellationToken)
                                ?? throw new PlatformAdminException("The primary domain is not active.",
                                    "primary_domain_not_active");
            var now = DateTimeOffset.UtcNow;
            var subscriptionActive = await dbContext.TenantSaasSubscriptions.AsNoTracking().AnyAsync(value =>
                value.TenantId == tenant.Id &&
                (value.Status == TenantSaasSubscriptionStatus.Trial ||
                 value.Status == TenantSaasSubscriptionStatus.Active) &&
                value.StartsAt <= now && value.CurrentPeriodEnd > now, cancellationToken);
            if (!subscriptionActive)
                throw new PlatformAdminException("An active trial or subscription is required.",
                    "subscription_inactive");

            var current = await dbContext.TenantOwnerActivationGrants
                .Where(value => value.TenantId == tenant.Id && value.UserId == ownerId &&
                                value.ConsumedAt == null && value.RevokedAt == null)
                .OrderByDescending(value => value.CreatedAt)
                .FirstOrDefaultAsync(cancellationToken);
            if (current is not null && current.ExpiresAt > now && !command.ReplaceExisting)
                throw new PlatformAdminException("An owner activation link is already active.",
                    "owner_activation_already_issued");
            if (current is not null)
            {
                current.RevokedAt = now;
                current.RevokedBy = actor.UserId;
                current.RevocationReason = command.ReplaceExisting
                    ? command.Reason.Trim()
                    : "Expired activation link replaced.";
            }

            var token = Base64Url(RandomNumberGenerator.GetBytes(32));
            var grant = new TenantOwnerActivationGrant
            {
                TenantId = tenant.Id,
                UserId = ownerId,
                CreatedBy = actor.UserId,
                DomainId = primaryDomain.Id,
                TokenHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(token))).ToLowerInvariant(),
                ExpiresAt = now.AddMinutes(provisioning.OwnerActivationMinutes)
            };
            dbContext.TenantOwnerActivationGrants.Add(grant);
            dbContext.PlatformOperationIdempotencies.Add(new PlatformOperationIdempotency
            {
                ActorUserId = actor.UserId,
                Scope = "platform.tenant.owner_activation.issue",
                IdempotencyKey = idempotencyKey,
                RequestHash = requestHash,
                ResourceId = grant.Id
            });
            AddAudit(dbContext, actor, "platform.tenant_owner_activation.issued", tenant.Id,
                new
                {
                    grant.Id, DomainId = primaryDomain.Id, grant.ExpiresAt, command.ReplaceExisting, command.Reason
                });
            await dbContext.SaveChangesAsync(cancellationToken);
            return new PlatformOwnerActivationLinkResult(
                grant.Id,
                BuildOwnerActivationUrl(primaryDomain.Host, grant.Id, token),
                grant.ExpiresAt,
                false);
        }, 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;
            tenant.Status = command.Status;
            AddAudit(dbContext, actor, "platform.tenant.status_changed", tenant.Id, new
            {
                FromStatus = fromStatus,
                ToStatus = tenant.Status,
                tenant.BillingStatus,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            await provider.GetRequiredService<ITenantFeatureCacheInvalidator>()
                .InvalidateAsync(tenant.Id, cancellationToken);
            await provider.GetRequiredService<IAuthorizationStateInvalidator>()
                .InvalidateTenantAsync(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<TenantBillingPolicyItem> GetTenantBillingPolicyAsync(
        PlatformAdminActor actor,
        Guid tenantId,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant billing policy get", async dbContext =>
        {
            await RequireTenantAsync(dbContext, tenantId, cancellationToken);
            var policy = await dbContext.TenantBillingPolicies.AsNoTracking()
                             .SingleOrDefaultAsync(value => value.TenantId == tenantId, cancellationToken)
                         ?? new TenantBillingPolicy { TenantId = tenantId };
            return ToBillingPolicyItem(policy);
        }, cancellationToken);
    }

    public async Task<TenantBillingPolicyItem> UpsertTenantBillingPolicyAsync(
        PlatformAdminActor actor,
        UpsertTenantBillingPolicyCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
        return await ExecuteSystemAsync("platform tenant billing policy upsert", async dbContext =>
        {
            await RequireTenantAsync(dbContext, command.TenantId, cancellationToken);
            if (string.IsNullOrWhiteSpace(command.Reason))
                throw new PlatformAdminException("Billing policy change reason is required.",
                    "platform_billing_reason_required");
            var policy = await dbContext.TenantBillingPolicies
                .SingleOrDefaultAsync(value => value.TenantId == command.TenantId, cancellationToken);
            if (policy is null)
            {
                policy = new TenantBillingPolicy { TenantId = command.TenantId };
                dbContext.TenantBillingPolicies.Add(policy);
            }

            policy.CollectionMode = command.CollectionMode;
            policy.DefaultPaymentProvider = NormalizeCode(command.DefaultPaymentProvider);
            policy.AutoGenerateRenewal = command.AutoGenerateRenewal;
            policy.RenewalLeadDays = Math.Clamp(command.RenewalLeadDays, 1, 90);
            AddAudit(dbContext, actor, "platform.tenant.billing_policy.updated", command.TenantId, new
            {
                policy.CollectionMode,
                policy.DefaultPaymentProvider,
                policy.AutoGenerateRenewal,
                policy.RenewalLeadDays,
                command.Reason
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return ToBillingPolicyItem(policy);
        }, cancellationToken);
    }
}