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 SavePointMallProductCommandHandler(
    IPointMallRepository repository,
    ITenantProvider tenantProvider)
    : IRequestHandler<SavePointMallProductCommand, MemberPointMallProductDto>
{
    /// <inheritdoc />
    public async Task<MemberPointMallProductDto> Handle(
        SavePointMallProductCommand request,
        CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var redeemType = MemberPointMallMapping.ParseRedeemType(request.RedeemType);
        var exchangeType = MemberPointMallMapping.ParseExchangeType(request.ExchangeType);
        var status = MemberPointMallMapping.ParseProductStatus(request.Status);
        var name = MemberPointMallMapping.NormalizeName(request.Name);
        var imageUrl = MemberPointMallMapping.NormalizeImageUrl(request.ImageUrl);
        var description = MemberPointMallMapping.NormalizeDescription(request.Description);
        var requiredPoints = MemberPointMallMapping.NormalizeRequiredPoints(request.RequiredPoints);
        var cashAmount = MemberPointMallMapping.NormalizeCashAmount(request.CashAmount, exchangeType);
        var stockTotal = MemberPointMallMapping.NormalizeStockTotal(request.StockTotal);
        var perMemberLimit = MemberPointMallMapping.NormalizePerMemberLimit(request.PerMemberLimit);
        var notifyChannels = MemberPointMallMapping.ParseNotifyChannels(request.NotifyChannels);

        var productId = (long?)null;
        var couponTemplateId = (long?)null;
        var physicalName = (string?)null;
        MemberPointMallPickupMethod? pickupMethod = null;

        switch (redeemType)
        {
            case MemberPointMallRedeemType.Product:
            {
                productId = request.ProductId.HasValue && request.ProductId.Value > 0
                    ? request.ProductId.Value
                    : throw new BusinessException(ErrorCodes.BadRequest, "兑换商品类型必须选择关联商品");
                break;
            }
            case MemberPointMallRedeemType.Coupon:
            {
                couponTemplateId = request.CouponTemplateId.HasValue && request.CouponTemplateId.Value > 0
                    ? request.CouponTemplateId.Value
                    : throw new BusinessException(ErrorCodes.BadRequest, "兑换优惠券类型必须选择关联优惠券");
                break;
            }
            case MemberPointMallRedeemType.Physical:
            {
                physicalName = MemberPointMallMapping.NormalizePhysicalName(request.PhysicalName);
                pickupMethod = MemberPointMallMapping.ParsePickupMethod(request.PickupMethod);
                break;
            }
            default:
            {
                throw new BusinessException(ErrorCodes.BadRequest, "redeemType 参数不合法");
            }
        }

        MemberPointMallProduct entity;
        if (request.PointMallProductId.HasValue && request.PointMallProductId.Value > 0)
        {
            entity = await repository.FindProductByIdAsync(
                tenantId,
                request.StoreId,
                request.PointMallProductId.Value,
                cancellationToken)
                ?? throw new BusinessException(ErrorCodes.NotFound, "兑换商品不存在");

            var redeemedCount = Math.Max(0, entity.StockTotal - entity.StockAvailable);
            if (stockTotal < redeemedCount)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "库存总量不能小于已兑换数量");
            }

            entity.Name = name;
            entity.ImageUrl = imageUrl;
            entity.RedeemType = redeemType;
            entity.ProductId = productId;
            entity.CouponTemplateId = couponTemplateId;
            entity.PhysicalName = physicalName;
            entity.PickupMethod = pickupMethod;
            entity.Description = description;
            entity.ExchangeType = exchangeType;
            entity.RequiredPoints = requiredPoints;
            entity.CashAmount = cashAmount;
            entity.StockTotal = stockTotal;
            entity.StockAvailable = stockTotal - redeemedCount;
            entity.PerMemberLimit = perMemberLimit;
            entity.NotifyChannelsJson = MemberPointMallMapping.SerializeNotifyChannels(notifyChannels);
            entity.Status = status;

            await repository.UpdateProductAsync(entity, cancellationToken);
        }
        else
        {
            entity = new MemberPointMallProduct
            {
                StoreId = request.StoreId,
                Name = name,
                ImageUrl = imageUrl,
                RedeemType = redeemType,
                ProductId = productId,
                CouponTemplateId = couponTemplateId,
                PhysicalName = physicalName,
                PickupMethod = pickupMethod,
                Description = description,
                ExchangeType = exchangeType,
                RequiredPoints = requiredPoints,
                CashAmount = cashAmount,
                StockTotal = stockTotal,
                StockAvailable = stockTotal,
                PerMemberLimit = perMemberLimit,
                NotifyChannelsJson = MemberPointMallMapping.SerializeNotifyChannels(notifyChannels),
                Status = status
            };

            await repository.AddProductAsync(entity, cancellationToken);
        }

        await repository.SaveChangesAsync(cancellationToken);

        var aggregates = await repository.GetProductAggregatesAsync(
            tenantId,
            request.StoreId,
            [entity.Id],
            cancellationToken);

        var aggregate = aggregates.TryGetValue(entity.Id, out var value)
            ? value
            : MemberPointMallDtoFactory.EmptyProductAggregate(entity.Id);

        return MemberPointMallDtoFactory.ToProductDto(entity, aggregate);
    }
}
