using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
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.Persistence;
using Tiku.Infrastructure.Tenancy;

namespace Tiku.Infrastructure.PlatformAdmin;

internal sealed partial class PlatformAdminService
{
    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 static bool IsPlatformOperationIdempotencyConflict(DbUpdateException exception)
    {
        return exception.InnerException is PostgresException
        {
            SqlState: PostgresErrorCodes.UniqueViolation,
            ConstraintName: { } constraintName
        } && constraintName.StartsWith(
            "ix_platform_operation_idempotencies_actor_user_id_scope_",
            StringComparison.Ordinal);
    }

    private async Task<PlatformTenantProvisioningResult> ProvisioningReplayResultAsync(
        TikuDbContext dbContext,
        Guid tenantId,
        CancellationToken cancellationToken)
    {
        var tenant = await dbContext.Tenants.AsNoTracking()
            .SingleAsync(value => value.Id == tenantId, cancellationToken);
        var ownerId = tenant.OwnerUserId
                      ?? throw new PlatformAdminException("Tenant owner was not found.", "tenant_owner_not_found");
        var owner = await dbContext.Users.AsNoTracking().SingleAsync(value => value.Id == ownerId, cancellationToken);
        var primaryDomain = await dbContext.TenantDomains.AsNoTracking()
            .SingleAsync(value => value.TenantId == tenant.Id && value.IsPrimary, cancellationToken);
        var subscriptionExpiresAt = await dbContext.TenantSaasSubscriptions.AsNoTracking()
            .Where(value => value.TenantId == tenant.Id)
            .OrderByDescending(value => value.CurrentPeriodEnd)
            .Select(value => (DateTimeOffset?)value.CurrentPeriodEnd)
            .FirstOrDefaultAsync(cancellationToken);
        return new PlatformTenantProvisioningResult(
            ToTenantItem(tenant, 0, subscriptionExpiresAt),
            ownerId,
            owner.Email ?? owner.Phone ?? owner.UserName ?? ownerId.ToString(),
            owner.ForcePasswordChange,
            ToDomainItemWithInstructions(primaryDomain),
            await OwnerActivationStatusAsync(dbContext, tenant, cancellationToken),
            true);
    }

    private static async Task<PlatformOwnerActivationLinkResult> OwnerActivationReplayResultAsync(
        TikuDbContext dbContext,
        Guid activationId,
        CancellationToken cancellationToken)
    {
        var grant = await dbContext.TenantOwnerActivationGrants.AsNoTracking()
            .SingleAsync(value => value.Id == activationId, cancellationToken);
        return new PlatformOwnerActivationLinkResult(grant.Id, null, grant.ExpiresAt, true);
    }

    private async Task<PlatformOwnerActivationStatus> OwnerActivationStatusAsync(
        TikuDbContext dbContext,
        Tenant tenant,
        CancellationToken cancellationToken)
    {
        if (tenant.OwnerUserId is not { } ownerId)
            return new PlatformOwnerActivationStatus("domain_pending", null, null);
        var activated = await dbContext.Users.AsNoTracking().AnyAsync(value =>
            value.Id == ownerId && value.PasswordHash != null && !value.ForcePasswordChange, cancellationToken);
        if (activated) return new PlatformOwnerActivationStatus("activated", null, null);
        var grant = await dbContext.TenantOwnerActivationGrants.AsNoTracking()
            .Where(value => value.TenantId == tenant.Id && value.UserId == ownerId &&
                            value.ConsumedAt == null && value.RevokedAt == null)
            .OrderByDescending(value => value.CreatedAt)
            .FirstOrDefaultAsync(cancellationToken);
        if (grant is not null)
            return new PlatformOwnerActivationStatus(
                grant.ExpiresAt > DateTimeOffset.UtcNow ? "issued" : "expired",
                grant.Id,
                grant.ExpiresAt);
        var domainActive = await dbContext.TenantDomains.AsNoTracking().AnyAsync(value =>
                value.TenantId == tenant.Id && value.IsPrimary && value.Status == TenantDomainStatus.Active,
            cancellationToken);
        return new PlatformOwnerActivationStatus(domainActive ? "ready_to_issue" : "domain_pending", null, null);
    }

    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"),
                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)
    {
        return new PlatformTenantItem(
            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)
    {
        return new PlatformTenantDomainItem(
            domain.Id,
            domain.TenantId,
            domain.Host,
            domain.DomainType,
            domain.Status,
            domain.IsPrimary,
            domain.VerifiedAt,
            domain.DnsVerifiedAt,
            domain.TlsReadyAt,
            domain.LastCheckedAt,
            domain.LastFailureReason,
            null,
            null,
            null);
    }

    private PlatformTenantDomainItem ToDomainItemWithInstructions(TenantDomain domain)
    {
        return ToDomainItem(domain) with
        {
            VerificationRecordName = $"{domains.VerificationRecordPrefix.Trim().TrimEnd('.')}.{domain.Host}",
            VerificationToken = domain.VerificationToken,
            CnameTarget = domains.AllowedCnameTargets.FirstOrDefault()
        };
    }

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

    private static TenantBillingProfileItem ToBillingProfileItem(TenantBillingProfile profile)
    {
        return new TenantBillingProfileItem(
            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 TenantBillingPolicyItem ToBillingPolicyItem(TenantBillingPolicy policy)
    {
        return new TenantBillingPolicyItem(
            policy.TenantId,
            policy.CollectionMode,
            policy.DefaultPaymentProvider,
            policy.AutoGenerateRenewal,
            policy.RenewalLeadDays,
            policy.CreatedAt,
            policy.UpdatedAt);
    }

    private static PlatformBillingDunningChannelItem ToDunningChannelItem(
        PlatformBillingDunningNotificationChannel channel)
    {
        return new PlatformBillingDunningChannelItem(
            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)
    {
        return new PlatformBillingDunningEventItem(
            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)
    {
        return new PlatformStaffItem(
            user.Id,
            user.Name,
            MaskPhone(user.Phone),
            user.Email,
            user.Status,
            roleCodes,
            user.CreatedAt,
            user.UpdatedAt);
    }

    private static int Limit(int? limit)
    {
        return Math.Clamp(limit ?? 50, 1, 200);
    }

    private static string NormalizeCode(string value)
    {
        return value.Trim().ToLowerInvariant();
    }

    private static string? Normalize(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

    private static string Required(string? value, string name)
    {
        return string.IsNullOrWhiteSpace(value)
            ? throw new PlatformAdminException($"{name} is required.", "idempotency_key_required")
            : value.Trim();
    }

    private static string ProvisioningRequestHash(CreatePlatformTenantCommand command)
    {
        var payload = JsonSerializer.Serialize(new
        {
            command.Slug,
            command.Name,
            command.LegalName,
            command.Status,
            command.BillingStatus,
            command.Metadata,
            command.PrimaryDomainHost,
            command.OwnerEmail,
            command.OwnerPhone,
            command.OwnerName,
            command.InitialOfferingVersionId,
            command.TrialDays,
            command.CollectionMode,
            command.DefaultPaymentProvider,
            command.AutoGenerateRenewal,
            command.RenewalLeadDays
        });
        return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(payload))).ToLowerInvariant();
    }

    private static string OwnerActivationRequestHash(IssuePlatformOwnerActivationLinkCommand command)
    {
        var payload = JsonSerializer.Serialize(new
        {
            command.TenantId,
            Reason = command.Reason.Trim(),
            command.ReplaceExisting
        });
        return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(payload))).ToLowerInvariant();
    }

    private static TenantDomain CreatePrimaryDomain(Guid tenantId, string host)
    {
        try
        {
            return TenantDomainProvisioning.CreatePrimary(tenantId, host);
        }
        catch (ArgumentException exception)
        {
            throw new PlatformAdminException(exception.Message, "tenant_domain_invalid");
        }
    }

    private static string Base64Url(byte[] value)
    {
        return Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_');
    }

    private string BuildOwnerActivationUrl(string host, Guid activationId, string token)
    {
        return TenantOwnerActivationUrlPolicy.Build(provisioning, host, activationId, token);
    }

    private static JsonElement JsonObjectOrDefault(JsonElement value)
    {
        return 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)
    {
        return Enum.TryParse<TenantStatus>(status, true, out var value) ? value : throw InvalidStatus(status);
    }

    private static TenantDomainStatus ParseDomainStatus(string status)
    {
        return 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 featureCodes = SaasFeatureCatalog.All.ToArray();
        var existingFeatureCodes = await dbContext.SaasFeatures
            .Where(value => featureCodes.Contains(value.Code))
            .Select(value => value.Code)
            .ToArrayAsync(cancellationToken);
        dbContext.SaasFeatures.AddRange(featureCodes
            .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 tenantPermissionCodes = BackendPermissions.Tenant.ToArray();
        var existingPermissionCodes = await dbContext.BackendPermissions
            .Where(value => tenantPermissionCodes.Contains(value.Code))
            .Select(value => value.Code)
            .ToArrayAsync(cancellationToken);
        dbContext.BackendPermissions.AddRange(tenantPermissionCodes
            .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(tenantPermissionCodes.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)
    {
        return Enum.TryParse<PlatformAuditAlertStatus>(status, true, out var value)
            ? value
            : throw InvalidStatus(status);
    }

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

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