using MediatR;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Tenants.Handlers;

/// <summary>
/// 配额校验处理器。
/// </summary>
public sealed class CheckTenantQuotaCommandHandler(
    ITenantRepository tenantRepository,
    ITenantPackageRepository packageRepository,
    ITenantQuotaUsageRepository quotaUsageRepository,
    ITenantProvider tenantProvider)
    : IRequestHandler<CheckTenantQuotaCommand, QuotaCheckResultDto>
{
    /// <inheritdoc />
    public async Task<QuotaCheckResultDto> Handle(CheckTenantQuotaCommand request, CancellationToken cancellationToken)
    {
        // 1. 校验请求参数
        if (request.Delta <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "配额消耗量必须大于 0");
        }

        // 2. 校验租户上下文
        var currentTenantId = tenantProvider.GetCurrentTenantId();
        if (currentTenantId == 0 || currentTenantId != request.TenantId)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "租户上下文不匹配，请在请求头 X-Tenant-Id 指定目标租户");
        }

        // 3. 获取租户与当前订阅
        _ = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
            ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");

        var subscription = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
        if (subscription == null || subscription.EffectiveTo <= DateTime.UtcNow)
        {
            throw new BusinessException(ErrorCodes.Conflict, "订阅不存在或已到期");
        }

        var package = await packageRepository.FindByIdAsync(subscription.TenantPackageId, cancellationToken)
            ?? throw new BusinessException(ErrorCodes.NotFound, "套餐不存在");

        var limit = ResolveLimit(package, request.QuotaType);

        // 4. 加载配额使用记录并计算
        var usage = await quotaUsageRepository.FindAsync(request.TenantId, request.QuotaType, cancellationToken)
                    ?? new TenantQuotaUsage
                    {
                        TenantId = request.TenantId,
                        QuotaType = request.QuotaType,
                        LimitValue = limit ?? 0,
                        UsedValue = 0,
                        ResetCycle = ResolveResetCycle(request.QuotaType)
                    };

        var usedAfter = usage.UsedValue + request.Delta;
        if (limit.HasValue && usedAfter > (decimal)limit.Value)
        {
            usage.LimitValue = limit.Value;
            await PersistUsageAsync(usage, quotaUsageRepository, cancellationToken);
            throw new BusinessException(ErrorCodes.Conflict, $"{request.QuotaType} 配额不足");
        }

        // 5. 更新使用并保存
        usage.LimitValue = limit ?? usage.LimitValue;
        usage.UsedValue = usedAfter;
        usage.ResetCycle ??= ResolveResetCycle(request.QuotaType);

        await PersistUsageAsync(usage, quotaUsageRepository, cancellationToken);

        // 6. 返回结果
        return new QuotaCheckResultDto
        {
            QuotaType = request.QuotaType,
            Limit = limit,
            Used = usage.UsedValue,
            Remaining = limit.HasValue ? limit.Value - usage.UsedValue : null
        };
    }

    private static decimal? ResolveLimit(TenantPackage package, TenantQuotaType quotaType)
    {
        return quotaType switch
        {
            TenantQuotaType.StoreCount => package.MaxStoreCount,
            TenantQuotaType.AccountCount => package.MaxAccountCount,
            TenantQuotaType.Storage => package.MaxStorageGb,
            TenantQuotaType.SmsCredits => package.MaxSmsCredits,
            TenantQuotaType.DeliveryOrders => package.MaxDeliveryOrders,
            _ => null
        };
    }

    private static string ResolveResetCycle(TenantQuotaType quotaType)
    {
        return quotaType switch
        {
            TenantQuotaType.SmsCredits => "monthly",
            TenantQuotaType.DeliveryOrders => "monthly",
            _ => "lifetime"
        };
    }

    private static async Task PersistUsageAsync(
        TenantQuotaUsage usage,
        ITenantQuotaUsageRepository quotaUsageRepository,
        CancellationToken cancellationToken)
    {
        // 判断是否为新增。
        if (usage.Id == 0)
        {
            await quotaUsageRepository.AddAsync(usage, cancellationToken);
        }
        else
        {
            await quotaUsageRepository.UpdateAsync(usage, cancellationToken);
        }

        await quotaUsageRepository.SaveChangesAsync(cancellationToken);
    }
}
