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

namespace TakeoutSaaS.Application.App.Coupons.FlashSale.Handlers;

/// <summary>
/// 保存限时折扣活动命令处理器。
/// </summary>
public sealed class SaveFlashSaleCampaignCommandHandler(
    IPromotionCampaignRepository promotionCampaignRepository,
    IProductRepository productRepository,
    ITenantProvider tenantProvider)
    : IRequestHandler<SaveFlashSaleCampaignCommand, FlashSaleDetailDto>
{
    /// <inheritdoc />
    public async Task<FlashSaleDetailDto> Handle(SaveFlashSaleCampaignCommand request, CancellationToken cancellationToken)
    {
        var normalizedName = request.Name.Trim();
        if (string.IsNullOrWhiteSpace(normalizedName))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "活动名称不能为空");
        }

        if (normalizedName.Length > 64)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "活动名称长度不能超过 64");
        }

        if (request.StoreIds.Count == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "storeIds 不能为空");
        }

        if (!request.StoreIds.Contains(request.OperationStoreId))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "storeIds 必须包含当前操作门店");
        }

        var tenantId = tenantProvider.GetCurrentTenantId();

        PromotionCampaign? campaign = null;
        FlashSaleMetricsDto? fallbackMetrics = null;
        var soldCountLookup = new Dictionary<long, int>();
        if (request.CampaignId.HasValue)
        {
            campaign = await promotionCampaignRepository.FindByIdAsync(
                request.CampaignId.Value,
                tenantId,
                PromotionType.FlashSale,
                cancellationToken)
                ?? throw new BusinessException(ErrorCodes.NotFound, "活动不存在");

            var existingRules = FlashSaleMapping.DeserializeRules(campaign.RulesJson, campaign.Id);
            if (!existingRules.StoreIds.Contains(request.OperationStoreId))
            {
                throw new BusinessException(ErrorCodes.NotFound, "活动不存在");
            }

            fallbackMetrics = existingRules.Metrics;
            soldCountLookup = existingRules.Products
                .GroupBy(item => item.ProductId)
                .ToDictionary(group => group.Key, group => Math.Max(0, group.First().SoldCount));
        }

        var saveProductItems = request.Products
            .Where(item => item.ProductId > 0)
            .GroupBy(item => item.ProductId)
            .Select(group => group.First())
            .ToList();

        if (saveProductItems.Count == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "折扣商品不能为空");
        }

        var productIds = saveProductItems.Select(item => item.ProductId).ToList();
        var products = await productRepository.GetByIdsAsync(
            tenantId,
            request.OperationStoreId,
            productIds,
            cancellationToken);

        if (products.Count != saveProductItems.Count)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "存在无效商品，请刷新后重试");
        }

        var categories = await productRepository.GetCategoriesByStoreAsync(
            tenantId,
            request.OperationStoreId,
            false,
            cancellationToken);
        var categoryNameLookup = categories.ToDictionary(item => item.Id, item => item.Name);

        var productLookup = products.ToDictionary(item => item.Id, item => item);
        var normalizedProducts = saveProductItems
            .Select(item =>
            {
                var product = productLookup[item.ProductId];
                var normalizedOriginalPrice = decimal.Round(product.Price, 2, MidpointRounding.AwayFromZero);
                if (item.DiscountPrice > normalizedOriginalPrice)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, $"商品[{product.Name}]折扣价不能高于原价");
                }

                soldCountLookup.TryGetValue(item.ProductId, out var soldCount);

                return new FlashSaleProductRuleDto
                {
                    ProductId = product.Id,
                    CategoryId = product.CategoryId,
                    CategoryName = categoryNameLookup.GetValueOrDefault(product.CategoryId, string.Empty),
                    Name = product.Name,
                    SpuCode = product.SpuCode,
                    Status = FlashSaleMapping.ToProductStatusText(product.Status, product.SoldoutMode),
                    OriginalPrice = normalizedOriginalPrice,
                    DiscountPrice = item.DiscountPrice,
                    PerUserLimit = item.PerUserLimit,
                    SoldCount = soldCount
                };
            })
            .ToList();

        var normalizedRules = FlashSaleMapping.NormalizeRulesForSave(
            request.CycleType,
            request.RecurringDateMode,
            request.StartDate,
            request.EndDate,
            request.TimeStart,
            request.TimeEnd,
            request.WeekDays,
            request.Channels,
            request.PerUserLimit,
            request.StoreIds,
            normalizedProducts,
            request.Metrics,
            fallbackMetrics);

        var nowUtc = DateTime.UtcNow;
        var campaignWindow = FlashSaleMapping.ResolveCampaignWindow(normalizedRules, nowUtc);
        if (campaign is null)
        {
            campaign = FlashSaleDtoFactory.CreateNewCampaign(
                normalizedName,
                campaignWindow.StartAt,
                campaignWindow.EndAt,
                FlashSaleMapping.SerializeRules(normalizedRules));

            await promotionCampaignRepository.AddAsync(campaign, cancellationToken);
            await promotionCampaignRepository.SaveChangesAsync(cancellationToken);
            return FlashSaleDtoFactory.ToDetailDto(campaign, normalizedRules, nowUtc);
        }

        campaign.Name = normalizedName;
        campaign.StartAt = campaignWindow.StartAt;
        campaign.EndAt = campaignWindow.EndAt;
        campaign.RulesJson = FlashSaleMapping.SerializeRules(normalizedRules);

        if (campaign.Status != PromotionStatus.Completed)
        {
            campaign.Status = PromotionStatus.Active;
        }

        await promotionCampaignRepository.UpdateAsync(campaign, cancellationToken);
        await promotionCampaignRepository.SaveChangesAsync(cancellationToken);
        return FlashSaleDtoFactory.ToDetailDto(campaign, normalizedRules, DateTime.UtcNow);
    }
}

