using MediatR;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Contracts;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Application.Messaging.Abstractions;
using TakeoutSaaS.Domain.Billings.Entities;
using TakeoutSaaS.Domain.Billings.Enums;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Events;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Security;

namespace TakeoutSaaS.Application.App.Tenants.Handlers;

/// <summary>
/// 后台手动新增租户命令处理器。
/// </summary>
/// <remarks>
/// 该操作涉及跨数据库（App + Identity）写入，采用 Saga 补偿模式保证数据一致性：
/// 1. 先在 Identity 库创建管理员账号、默认角色（tenant-admin）并分配角色
/// 2. 再在 App 库创建租户、订阅、认证资料
/// 3. 如果步骤 2 失败，回滚步骤 1（删除用户角色关系、角色、管理员账号）
/// </remarks>
public sealed class CreateTenantManuallyCommandHandler(
    ITenantRepository tenantRepository,
    ITenantPackageRepository tenantPackageRepository,
    IIdentityUserRepository identityUserRepository,
    IRoleRepository roleRepository,
    IUserRoleRepository userRoleRepository,
    IPasswordHasher<IdentityUser> passwordHasher,
    ICurrentUserAccessor currentUserAccessor,
    IIdGenerator idGenerator,
    IMediator mediator,
    IEventPublisher eventPublisher,
    ILogger<CreateTenantManuallyCommandHandler> logger)
    : IRequestHandler<CreateTenantManuallyCommand, TenantDetailDto>
{
    /// <inheritdoc />
    public async Task<TenantDetailDto> Handle(CreateTenantManuallyCommand request, CancellationToken cancellationToken)
    {
        // 1. 规范化输入
        var code = request.Code.Trim();
        var name = request.Name.Trim();
        var adminAccount = request.AdminAccount.Trim();
        var adminDisplayName = request.AdminDisplayName.Trim();

        // 2. 解析套餐 ID
        if (!long.TryParse(request.TenantPackageId, out var tenantPackageId) || tenantPackageId <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "套餐 ID 无效");
        }

        // 3. 校验租户编码唯一性
        if (await tenantRepository.ExistsByCodeAsync(code, null, cancellationToken))
        {
            throw new BusinessException(ErrorCodes.Conflict, "租户编码已存在");
        }

        // 4. 校验套餐存在性
        var package = await tenantPackageRepository.GetByIdAsync(tenantPackageId, cancellationToken);
        if (package is null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "套餐不存在");
        }

        // 5. 生成租户 ID
        var tenantId = idGenerator.NextId();

        // 6. 校验管理员账号唯一性（租户内）
        if (await identityUserRepository.ExistsByAccountAsync(PortalType.Tenant, tenantId, adminAccount, null, cancellationToken))
        {
            throw new BusinessException(ErrorCodes.Conflict, "管理员账号已存在");
        }

        // 7. 计算订阅时间
        var effectiveFrom = request.SubscriptionEffectiveFrom ?? DateTime.UtcNow;
        var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths);
        var nextBillingDate = request.NextBillingDate ?? effectiveTo;

        // 8. 创建管理员账号实体
        var adminUser = new IdentityUser
        {
            Id = idGenerator.NextId(),
            Portal = PortalType.Tenant,
            TenantId = tenantId,
            Account = adminAccount,
            DisplayName = adminDisplayName,
            PasswordHash = string.Empty,
            Phone = request.ContactPhone?.Trim(),
            Email = request.ContactEmail?.Trim(),
            Status = IdentityUserStatus.Active,
            FailedLoginCount = 0,
            LockedUntil = null,
            LastLoginAt = null,
            MustChangePassword = false,
            MerchantId = null,
            Avatar = request.AdminAvatar?.Trim()
        };
        adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, request.AdminPassword);

        // 9. 【Saga 步骤 1】先在 Identity 库创建管理员账号
        await identityUserRepository.AddAsync(adminUser, cancellationToken);
        await identityUserRepository.SaveChangesAsync(cancellationToken);
        logger.LogInformation("租户 {TenantId} 管理员账号 {AdminUserId} 创建成功", tenantId, adminUser.Id);

        // 10. 创建租户默认角色（tenant-admin）
        var defaultRole = new Role
        {
            Id = idGenerator.NextId(),
            Portal = PortalType.Tenant,
            TenantId = tenantId,
            Code = "tenant-admin",
            Name = "租户管理员",
            Description = "租户默认管理员角色，拥有租户端全部权限"
        };
        await roleRepository.AddAsync(defaultRole, cancellationToken);
        await roleRepository.SaveChangesAsync(cancellationToken);
        logger.LogInformation("租户 {TenantId} 默认角色 {RoleId} 创建成功", tenantId, defaultRole.Id);

        // 11. 为管理员用户分配默认角色
        await userRoleRepository.ReplaceUserRolesAsync(PortalType.Tenant, tenantId, adminUser.Id, [defaultRole.Id], cancellationToken);
        await userRoleRepository.SaveChangesAsync(cancellationToken);
        logger.LogInformation("租户 {TenantId} 管理员 {AdminUserId} 已分配角色 {RoleId}", tenantId, adminUser.Id, defaultRole.Id);

        try
        {
            // 12. 根据是否跳过审核确定租户状态和认证状态
            var tenantStatus = request.IsSkipApproval ? TenantStatus.Active : TenantStatus.PendingReview;
            var verificationStatus = request.IsSkipApproval ? TenantVerificationStatus.Approved : TenantVerificationStatus.Pending;

            // 13. 创建租户实体
            var tenant = new Tenant
            {
                Id = tenantId,
                Code = code,
                Name = name,
                ShortName = request.ShortName?.Trim(),
                LegalEntityName = request.LegalEntityName?.Trim(),
                Industry = request.Industry?.Trim(),
                LogoUrl = request.LogoUrl?.Trim(),
                Country = request.Country?.Trim(),
                Province = request.Province?.Trim(),
                City = request.City?.Trim(),
                Address = request.Address?.Trim(),
                ContactName = request.ContactName?.Trim(),
                ContactPhone = request.ContactPhone?.Trim(),
                ContactEmail = request.ContactEmail?.Trim(),
                Tags = request.Tags?.Trim(),
                Remarks = request.Remarks?.Trim(),
                Status = tenantStatus,
                EffectiveFrom = effectiveFrom,
                EffectiveTo = effectiveTo,
                PrimaryOwnerUserId = adminUser.Id
            };

            // 14. 创建订阅实体
            var subscription = new TenantSubscription
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                TenantPackageId = tenantPackageId,
                Status = request.SubscriptionStatus,
                EffectiveFrom = effectiveFrom,
                EffectiveTo = effectiveTo,
                NextBillingDate = nextBillingDate,
                AutoRenew = request.AutoRenew,
                Notes = request.SubscriptionNotes?.Trim()
            };

            // 15. 创建认证资料实体
            var verification = new TenantVerificationProfile
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                Status = verificationStatus,
                BusinessLicenseNumber = request.BusinessLicenseNumber?.Trim(),
                BusinessLicenseUrl = request.BusinessLicenseUrl?.Trim(),
                LegalPersonName = request.LegalPersonName?.Trim(),
                LegalPersonIdNumber = request.LegalPersonIdNumber?.Trim(),
                LegalPersonIdFrontUrl = request.LegalPersonIdFrontUrl?.Trim(),
                LegalPersonIdBackUrl = request.LegalPersonIdBackUrl?.Trim(),
                BankAccountName = request.BankAccountName?.Trim(),
                BankAccountNumber = request.BankAccountNumber?.Trim(),
                BankName = request.BankName?.Trim(),
                SubmittedAt = DateTime.UtcNow,
                ReviewedAt = request.IsSkipApproval ? DateTime.UtcNow : null,
                ReviewedBy = request.IsSkipApproval ? currentUserAccessor.UserId : null
            };

            // 16. 根据套餐配额创建配额使用记录
            var quotaUsages = CreateQuotaUsagesFromPackage(tenantId, package);

            // 17. 创建账单记录和支付记录（可选）
            TenantBillingStatement? billing = null;
            TenantPayment? payment = null;
            if (request.CreateBilling)
            {
                billing = CreateBillingFromSubscription(
                    tenantId,
                    subscription.Id,
                    package,
                    quotaUsages,
                    effectiveFrom,
                    effectiveTo,
                    request.DurationMonths,
                    request.BillingAmount,
                    request.BillingDiscountAmount ?? 0,
                    request.BillingStatus,
                    request.BillingNotes);

                // 18. 如果账单状态为已支付，创建支付记录
                if (request.BillingStatus == TenantBillingStatus.Paid && billing.AmountDue > 0)
                {
                    payment = CreatePaymentFromBilling(tenantId, billing);
                }
            }

            // 19. 【Saga 步骤 2】在 App 库创建租户、订阅、认证资料、配额使用记录、账单、支付记录（使用事务）
            await tenantRepository.CreateTenantWithRelatedDataAsync(tenant, subscription, verification, quotaUsages, billing, payment, cancellationToken);
            logger.LogInformation("租户 {TenantId} 及相关数据创建成功，跳过审核：{IsSkipApproval}", tenantId, request.IsSkipApproval);

            // 20. 发布租户创建完成事件（用于自动创建商户和证照）
            var tenantCreatedEvent = new TenantCreatedEvent
            {
                TenantId = tenantId,
                Code = code,
                Name = name,
                ShortName = request.ShortName?.Trim(),
                LegalEntityName = request.LegalEntityName?.Trim(),
                LogoUrl = request.LogoUrl?.Trim(),
                Province = request.Province?.Trim(),
                City = request.City?.Trim(),
                Address = request.Address?.Trim(),
                ContactName = request.ContactName?.Trim(),
                ContactPhone = request.ContactPhone?.Trim(),
                ContactEmail = request.ContactEmail?.Trim(),
                BusinessLicenseNumber = request.BusinessLicenseNumber?.Trim(),
                BusinessLicenseUrl = request.BusinessLicenseUrl?.Trim(),
                LegalPersonName = request.LegalPersonName?.Trim(),
                LegalPersonIdNumber = request.LegalPersonIdNumber?.Trim(),
                LegalPersonIdFrontUrl = request.LegalPersonIdFrontUrl?.Trim(),
                LegalPersonIdBackUrl = request.LegalPersonIdBackUrl?.Trim(),
                AdminUserId = adminUser.Id,
                IsSkipApproval = request.IsSkipApproval,
                CreatedAt = DateTime.UtcNow
            };
            await eventPublisher.PublishAsync("tenant.created", tenantCreatedEvent, cancellationToken);
            logger.LogInformation("租户 {TenantId} 创建事件已发布", tenantId);
        }
        catch (Exception ex)
        {
            // 18. 【Saga 补偿】App 库操作失败，回滚 Identity 库的管理员账号、角色和用户角色关系
            // 记录完整异常信息（包括内部异常）
            var fullErrorMessage = ex.InnerException?.Message ?? ex.Message;
            logger.LogError(ex, "租户 {TenantId} 创建失败，错误详情：{ErrorDetail}，开始回滚 Identity 数据",
                tenantId, fullErrorMessage);

            try
            {
                // 19. 回滚用户角色关系（清空用户的角色）
                await userRoleRepository.ReplaceUserRolesAsync(PortalType.Tenant, tenantId, adminUser.Id, [], cancellationToken);
                await userRoleRepository.SaveChangesAsync(cancellationToken);
                logger.LogInformation("用户角色关系回滚成功，用户 {AdminUserId}", adminUser.Id);

                // 20. 回滚默认角色
                await roleRepository.DeleteAsync(PortalType.Tenant, tenantId, defaultRole.Id, cancellationToken);
                await roleRepository.SaveChangesAsync(cancellationToken);
                logger.LogInformation("默认角色 {RoleId} 回滚成功", defaultRole.Id);

                // 21. 回滚管理员账号
                await identityUserRepository.RemoveAsync(adminUser, cancellationToken);
                await identityUserRepository.SaveChangesAsync(cancellationToken);
                logger.LogInformation("管理员账号 {AdminUserId} 回滚成功", adminUser.Id);
            }
            catch (Exception rollbackEx)
            {
                // 22. 补偿失败，记录严重错误（需要人工介入）
                logger.LogCritical(
                    rollbackEx,
                    "严重：租户 {TenantId} 创建失败且 Identity 数据回滚失败，需要人工介入清理（用户 {AdminUserId}，角色 {RoleId}）",
                    tenantId,
                    adminUser.Id,
                    defaultRole.Id);
            }

            // 23. 重新抛出业务异常（包含详细错误信息）
            throw new BusinessException(ErrorCodes.InternalServerError, $"创建租户失败：{fullErrorMessage}");
        }

        // 24. 查询并返回租户详情
        var detail = await mediator.Send(new GetTenantDetailQuery { TenantId = tenantId }, cancellationToken);
        return detail ?? throw new BusinessException(ErrorCodes.InternalServerError, "创建租户成功但查询详情失败");
    }

    /// <summary>
    /// 根据套餐配额创建配额使用记录列表。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="package">套餐实体。</param>
    /// <returns>配额使用记录列表。</returns>
    private List<TenantQuotaUsage> CreateQuotaUsagesFromPackage(long tenantId, TenantPackage package)
    {
        var quotaUsages = new List<TenantQuotaUsage>();
        var now = DateTime.UtcNow;

        // 1. 门店数量配额
        if (package.MaxStoreCount.HasValue)
        {
            quotaUsages.Add(new TenantQuotaUsage
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                QuotaType = TenantQuotaType.Store,
                LimitValue = package.MaxStoreCount.Value,
                UsedValue = 0,
                ResetCycle = null,
                LastResetAt = now
            });
        }

        // 2. 账号数量配额
        if (package.MaxAccountCount.HasValue)
        {
            quotaUsages.Add(new TenantQuotaUsage
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                QuotaType = TenantQuotaType.Account,
                LimitValue = package.MaxAccountCount.Value,
                UsedValue = 0,
                ResetCycle = null,
                LastResetAt = now
            });
        }

        // 3. 存储空间配额
        if (package.MaxStorageGb.HasValue)
        {
            quotaUsages.Add(new TenantQuotaUsage
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                QuotaType = TenantQuotaType.StorageGb,
                LimitValue = package.MaxStorageGb.Value,
                UsedValue = 0,
                ResetCycle = null,
                LastResetAt = now
            });
        }

        // 4. 短信额度配额（按月重置）
        if (package.MaxSmsCredits.HasValue)
        {
            quotaUsages.Add(new TenantQuotaUsage
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                QuotaType = TenantQuotaType.SmsCredits,
                LimitValue = package.MaxSmsCredits.Value,
                UsedValue = 0,
                ResetCycle = "monthly",
                LastResetAt = now
            });
        }

        // 5. 配送订单数配额（按月重置）
        if (package.MaxDeliveryOrders.HasValue)
        {
            quotaUsages.Add(new TenantQuotaUsage
            {
                Id = idGenerator.NextId(),
                TenantId = tenantId,
                QuotaType = TenantQuotaType.DeliveryOrders,
                LimitValue = package.MaxDeliveryOrders.Value,
                UsedValue = 0,
                ResetCycle = "monthly",
                LastResetAt = now
            });
        }

        return quotaUsages;
    }

    /// <summary>
    /// 根据订阅信息创建账单实体。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="subscriptionId">订阅 ID。</param>
    /// <param name="package">套餐实体。</param>
    /// <param name="quotaUsages">配额使用记录列表。</param>
    /// <param name="periodStart">账单周期开始时间。</param>
    /// <param name="periodEnd">账单周期结束时间。</param>
    /// <param name="durationMonths">订阅时长（月）。</param>
    /// <param name="billingAmount">指定账单金额（可选）。</param>
    /// <param name="discountAmount">折扣金额。</param>
    /// <param name="status">账单状态。</param>
    /// <param name="notes">账单备注。</param>
    /// <returns>账单实体。</returns>
    private TenantBillingStatement CreateBillingFromSubscription(
        long tenantId,
        long subscriptionId,
        TenantPackage package,
        IReadOnlyList<TenantQuotaUsage> quotaUsages,
        DateTime periodStart,
        DateTime periodEnd,
        int durationMonths,
        decimal? billingAmount,
        decimal discountAmount,
        TenantBillingStatus status,
        string? notes)
    {
        // 1. 构建账单明细列表
        var lineItems = new List<BillingLineItem>();

        // 2. 添加套餐费用明细
        decimal packageAmount = 0;
        if (durationMonths >= 12 && package.YearlyPrice.HasValue)
        {
            var years = durationMonths / 12;
            var remainingMonths = durationMonths % 12;
            packageAmount = package.YearlyPrice.Value * years;

            lineItems.Add(new BillingLineItem
            {
                Name = $"{package.Name}（年付 × {years}年）",
                UnitPrice = package.YearlyPrice.Value,
                Quantity = years,
                Amount = package.YearlyPrice.Value * years
            });

            if (remainingMonths > 0 && package.MonthlyPrice.HasValue)
            {
                var monthlyAmount = package.MonthlyPrice.Value * remainingMonths;
                packageAmount += monthlyAmount;
                lineItems.Add(new BillingLineItem
                {
                    Name = $"{package.Name}（月付 × {remainingMonths}月）",
                    UnitPrice = package.MonthlyPrice.Value,
                    Quantity = remainingMonths,
                    Amount = monthlyAmount
                });
            }
        }
        else if (package.MonthlyPrice.HasValue)
        {
            packageAmount = package.MonthlyPrice.Value * durationMonths;
            lineItems.Add(new BillingLineItem
            {
                Name = $"{package.Name}（月付 × {durationMonths}月）",
                UnitPrice = package.MonthlyPrice.Value,
                Quantity = durationMonths,
                Amount = packageAmount
            });
        }
        else
        {
            lineItems.Add(new BillingLineItem
            {
                Name = $"{package.Name}（免费套餐）",
                UnitPrice = 0,
                Quantity = durationMonths,
                Amount = 0
            });
        }

        // 3. 添加配额包明细（免费赠送）
        foreach (var quota in quotaUsages)
        {
            var quotaName = quota.QuotaType switch
            {
                TenantQuotaType.Store => $"门店配额（{quota.LimitValue}个）",
                TenantQuotaType.Account => $"账号配额（{quota.LimitValue}个）",
                TenantQuotaType.StorageGb => $"存储空间（{quota.LimitValue}GB）",
                TenantQuotaType.SmsCredits => $"短信额度（{quota.LimitValue}条/月）",
                TenantQuotaType.DeliveryOrders => $"配送订单（{quota.LimitValue}单/月）",
                _ => $"配额（{quota.LimitValue}）"
            };

            lineItems.Add(new BillingLineItem
            {
                Name = quotaName,
                UnitPrice = 0,
                Quantity = 1,
                Amount = 0,
                Remark = "套餐赠送"
            });
        }

        // 4. 添加折扣明细
        if (discountAmount > 0)
        {
            lineItems.Add(new BillingLineItem
            {
                Name = "优惠折扣",
                UnitPrice = -discountAmount,
                Quantity = 1,
                Amount = -discountAmount
            });
        }

        // 5. 计算账单金额
        decimal amountDue = billingAmount ?? Math.Max(0, packageAmount - discountAmount);

        // 6. 生成账单编号：BILL-{YYYYMMDD}-{ID后8位}
        var billingId = idGenerator.NextId();
        var datePart = DateTime.UtcNow.ToString("yyyyMMdd");
        var idSuffix = billingId.ToString().PadLeft(8, '0')[^8..];
        var statementNo = $"BILL-{datePart}-{idSuffix}";

        // 7. 序列化账单明细为 JSON
        var lineItemsJson = System.Text.Json.JsonSerializer.Serialize(lineItems);

        // 8. 创建账单实体
        return new TenantBillingStatement
        {
            Id = billingId,
            TenantId = tenantId,
            StatementNo = statementNo,
            BillingType = TenantBillingType.Subscription,
            PeriodStart = periodStart,
            PeriodEnd = periodEnd,
            AmountDue = amountDue,
            AmountPaid = status == TenantBillingStatus.Paid ? amountDue : 0,
            DiscountAmount = discountAmount,
            TaxAmount = 0,
            Currency = "CNY",
            Status = status,
            DueDate = periodEnd,
            LineItemsJson = lineItemsJson,
            Notes = notes?.Trim(),
            SubscriptionId = subscriptionId,
            CreatedAt = DateTime.UtcNow
        };
    }

    /// <summary>
    /// 根据账单创建支付记录。
    /// </summary>
    /// <param name="tenantId">租户 ID。</param>
    /// <param name="billing">账单实体。</param>
    /// <returns>支付记录实体。</returns>
    private TenantPayment CreatePaymentFromBilling(long tenantId, TenantBillingStatement billing)
    {
        return new TenantPayment
        {
            Id = idGenerator.NextId(),
            TenantId = tenantId,
            BillingStatementId = billing.Id,
            Amount = billing.AmountDue,
            Method = TenantPaymentMethod.Other,
            Status = TenantPaymentStatus.Success,
            TransactionNo = $"MANUAL-{billing.StatementNo}",
            PaidAt = DateTime.UtcNow,
            Notes = "手动创建租户时自动生成",
            CreatedAt = DateTime.UtcNow
        };
    }
}

/// <summary>
/// 账单明细项。
/// </summary>
internal sealed class BillingLineItem
{
    /// <summary>
    /// 项目名称。
    /// </summary>
    public string Name { get; init; } = string.Empty;

    /// <summary>
    /// 单价。
    /// </summary>
    public decimal UnitPrice { get; init; }

    /// <summary>
    /// 数量。
    /// </summary>
    public int Quantity { get; init; }

    /// <summary>
    /// 金额。
    /// </summary>
    public decimal Amount { get; init; }

    /// <summary>
    /// 备注。
    /// </summary>
    public string? Remark { get; init; }
}
