using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Inventory.Commands;
using TakeoutSaaS.Application.App.Inventory.Dto;
using TakeoutSaaS.Domain.Inventory.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Inventory.Handlers;

/// <summary>
/// 库存扣减处理器。
/// </summary>
public sealed class DeductInventoryCommandHandler(
    IInventoryRepository inventoryRepository,
    ITenantProvider tenantProvider,
    ILogger<DeductInventoryCommandHandler> logger)
    : IRequestHandler<DeductInventoryCommand, InventoryItemDto>
{
    /// <inheritdoc />
    public async Task<InventoryItemDto> Handle(DeductInventoryCommand request, CancellationToken cancellationToken)
    {
        // 1. 读取库存
        var tenantId = tenantProvider.GetCurrentTenantId();
        var item = await inventoryRepository.GetForUpdateAsync(tenantId, request.StoreId, request.ProductSkuId, cancellationToken);
        if (item is null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "库存不存在");
        }

        // 1.1 幂等：若锁记录已扣减/释放则直接返回
        if (!string.IsNullOrWhiteSpace(request.IdempotencyKey))
        {
            var lockRecord = await inventoryRepository.FindLockByKeyAsync(tenantId, request.IdempotencyKey, cancellationToken);
            if (lockRecord is not null)
            {
                if (lockRecord.Status == Domain.Inventory.Enums.InventoryLockStatus.Deducted)
                {
                    return InventoryMapping.ToDto(item);
                }

                if (lockRecord.Status == Domain.Inventory.Enums.InventoryLockStatus.Locked)
                {
                    request = request with { Quantity = lockRecord.Quantity, IsPresaleOrder = lockRecord.IsPresale };
                    await inventoryRepository.MarkLockStatusAsync(lockRecord, Domain.Inventory.Enums.InventoryLockStatus.Deducted, cancellationToken);
                }
            }
        }

        // 2. 计算扣减来源
        var isPresale = request.IsPresaleOrder || item.IsPresale;
        if (isPresale)
        {
            if (item.PresaleLocked < request.Quantity)
            {
                throw new BusinessException(ErrorCodes.Conflict, "预售锁定不足，无法扣减");
            }

            item.PresaleLocked -= request.Quantity;
        }
        else
        {
            if (item.QuantityReserved < request.Quantity)
            {
                throw new BusinessException(ErrorCodes.Conflict, "锁定库存不足，无法扣减");
            }

            item.QuantityReserved -= request.Quantity;
        }

        var remaining = item.QuantityOnHand - request.Quantity;
        if (remaining < 0)
        {
            throw new BusinessException(ErrorCodes.Conflict, "可用库存不足，无法扣减");
        }

        // 3. 扣减可用量并按批次消耗
        item.QuantityOnHand = remaining;
        // 3.1 批次扣减（非预售）
        if (!isPresale)
        {
            var batches = await inventoryRepository.GetBatchesForConsumeAsync(tenantId, request.StoreId, request.ProductSkuId, item.BatchConsumeStrategy, cancellationToken);
            var need = request.Quantity;
            foreach (var batch in batches)
            {
                if (need <= 0)
                {
                    break;
                }

                var take = Math.Min(batch.RemainingQuantity, need);
                batch.RemainingQuantity -= take;
                need -= take;
                await inventoryRepository.UpdateBatchAsync(batch, cancellationToken);
            }

            if (need > 0)
            {
                throw new BusinessException(ErrorCodes.Conflict, "批次数量不足，无法扣减");
            }
        }

        item.IsSoldOut = item.QuantityOnHand - item.QuantityReserved - item.PresaleLocked <= (item.SafetyStock ?? 0);
        await inventoryRepository.UpdateItemAsync(item, cancellationToken);
        await inventoryRepository.SaveChangesAsync(cancellationToken);
        logger.LogInformation("扣减库存 门店 {StoreId} SKU {ProductSkuId} 数量 {Quantity}", request.StoreId, request.ProductSkuId, request.Quantity);
        return InventoryMapping.ToDto(item);
    }
}
