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

namespace TakeoutSaaS.Application.App.Members.PointsMall.Handlers;

/// <summary>
/// 写入积分商城兑换记录处理器。
/// </summary>
public sealed class WritePointMallRecordCommandHandler(
    IPointMallRepository repository,
    IMemberRepository memberRepository,
    ITenantProvider tenantProvider)
    : IRequestHandler<WritePointMallRecordCommand, MemberPointMallRecordDto>
{
    /// <inheritdoc />
    public async Task<MemberPointMallRecordDto> Handle(
        WritePointMallRecordCommand request,
        CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var redeemedAt = request.RedeemedAt.HasValue
            ? MemberPointMallMapping.NormalizeUtc(request.RedeemedAt.Value)
            : DateTime.UtcNow;

        var product = await repository.FindProductByIdAsync(
            tenantId,
            request.StoreId,
            request.PointMallProductId,
            cancellationToken)
            ?? throw new BusinessException(ErrorCodes.NotFound, "兑换商品不存在");

        if (product.Status != MemberPointMallProductStatus.Enabled)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "兑换商品未上架");
        }

        if (product.StockAvailable <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "兑换商品库存不足");
        }

        var member = await memberRepository.FindProfileByIdAsync(
            tenantId,
            request.MemberId,
            cancellationToken)
            ?? throw new BusinessException(ErrorCodes.NotFound, "会员不存在");

        var usedPoints = MemberPointMallMapping.NormalizeRequiredPoints(product.RequiredPoints);
        if (member.PointsBalance < usedPoints)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "会员积分不足");
        }

        if (product.PerMemberLimit.HasValue && product.PerMemberLimit.Value > 0)
        {
            var redeemedCount = await repository.CountMemberRedeemsByProductAsync(
                tenantId,
                request.StoreId,
                product.Id,
                member.Id,
                cancellationToken);

            if (redeemedCount >= product.PerMemberLimit.Value)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "已达到每人限兑次数");
            }
        }

        member.PointsBalance -= usedPoints;
        await memberRepository.UpdateProfileAsync(member, cancellationToken);

        product.StockAvailable -= 1;
        await repository.UpdateProductAsync(product, cancellationToken);

        var initialStatus = MemberPointMallMapping.ResolveRecordInitialStatus(product.RedeemType);
        var record = new MemberPointMallRecord
        {
            StoreId = request.StoreId,
            RecordNo = MemberPointMallMapping.BuildRecordNo(redeemedAt),
            PointMallProductId = product.Id,
            MemberId = member.Id,
            MemberName = MemberPointMallMapping.ResolveMemberName(member),
            MemberMobileMasked = MemberPointMallMapping.ResolveMemberMobileMasked(member),
            ProductName = product.Name,
            RedeemType = product.RedeemType,
            ExchangeType = product.ExchangeType,
            UsedPoints = usedPoints,
            CashAmount = product.CashAmount,
            Status = initialStatus,
            RedeemedAt = redeemedAt,
            IssuedAt = MemberPointMallMapping.ResolveRecordInitialIssuedAt(product.RedeemType, redeemedAt)
        };

        await repository.AddRecordAsync(record, cancellationToken);

        var ledger = new MemberPointLedger
        {
            MemberId = member.Id,
            ChangeAmount = -usedPoints,
            BalanceAfterChange = member.PointsBalance,
            Reason = PointChangeReason.Redeem,
            SourceId = product.Id,
            OccurredAt = redeemedAt
        };

        await repository.AddPointLedgerAsync(ledger, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);

        return MemberPointMallDtoFactory.ToRecordDto(record);
    }
}
