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

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

/// <summary>
/// 写入次卡使用记录处理器。
/// </summary>
public sealed class WritePunchCardUsageRecordCommandHandler(
    IPunchCardRepository repository,
    ITenantProvider tenantProvider)
    : IRequestHandler<WritePunchCardUsageRecordCommand, PunchCardUsageRecordDto>
{
    /// <inheritdoc />
    public async Task<PunchCardUsageRecordDto> Handle(
        WritePunchCardUsageRecordCommand request,
        CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var usedAt = request.UsedAt.HasValue
            ? PunchCardMapping.NormalizeUtc(request.UsedAt.Value)
            : DateTime.UtcNow;

        var template = await repository.FindTemplateByIdAsync(
            tenantId,
            request.StoreId,
            request.TemplateId,
            cancellationToken)
            ?? throw new BusinessException(ErrorCodes.NotFound, "次卡不存在");

        if (template.Status != PunchCardStatus.Enabled)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "次卡已下架，无法使用");
        }

        var productName = PunchCardMapping.NormalizeProductName(request.ProductName);
        var usedTimes = PunchCardMapping.NormalizeRequiredPositiveInt(request.UsedTimes, "usedTimes", template.TotalTimes);
        var extraPayAmount = PunchCardMapping.NormalizeOptionalAmount(request.ExtraPayAmount, "extraPayAmount", true);

        PunchCardInstance? instance = null;
        if (request.InstanceId.HasValue && request.InstanceId.Value > 0)
        {
            instance = await repository.FindInstanceByIdAsync(
                tenantId,
                request.StoreId,
                request.InstanceId.Value,
                cancellationToken);
        }
        else if (!string.IsNullOrWhiteSpace(request.InstanceNo))
        {
            var normalizedInstanceNo = PunchCardMapping.NormalizeInstanceNo(request.InstanceNo);
            instance = await repository.FindInstanceByNoAsync(
                tenantId,
                request.StoreId,
                normalizedInstanceNo,
                cancellationToken);
        }

        if (instance is not null && instance.PunchCardTemplateId != template.Id)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "次卡实例与模板不匹配");
        }

        var isNewInstance = false;
        if (instance is null)
        {
            var memberName = PunchCardMapping.NormalizeMemberName(request.MemberName);
            var memberPhoneMasked = PunchCardMapping.NormalizeMemberPhoneMasked(request.MemberPhoneMasked);
            var purchasedAt = usedAt;

            instance = new PunchCardInstance
            {
                StoreId = request.StoreId,
                PunchCardTemplateId = template.Id,
                InstanceNo = PunchCardDtoFactory.GenerateInstanceNo(usedAt),
                MemberName = memberName,
                MemberPhoneMasked = memberPhoneMasked,
                PurchasedAt = purchasedAt,
                ExpiresAt = PunchCardMapping.ResolveInstanceExpireAt(template, purchasedAt),
                TotalTimes = template.TotalTimes,
                RemainingTimes = template.TotalTimes,
                PaidAmount = template.SalePrice,
                Status = PunchCardInstanceStatus.Active
            };
            isNewInstance = true;
        }

        if (PunchCardMapping.IsInstanceExpired(instance, usedAt))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "次卡已过期");
        }

        if (instance.Status == PunchCardInstanceStatus.Refunded)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "次卡已退款");
        }

        if (instance.RemainingTimes <= 0 || instance.Status == PunchCardInstanceStatus.UsedUp)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "次卡已用完");
        }

        if (template.PerOrderLimit.HasValue && usedTimes > template.PerOrderLimit.Value)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "超出每单限用次数");
        }

        if (usedTimes > instance.RemainingTimes)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "超出次卡剩余次数");
        }

        var remainingTimes = instance.RemainingTimes - usedTimes;
        var statusAfterUse = PunchCardMapping.ResolveUsageRecordStatus(instance, remainingTimes, usedAt);

        instance.RemainingTimes = remainingTimes;
        instance.Status = statusAfterUse switch
        {
            PunchCardUsageRecordStatus.UsedUp => PunchCardInstanceStatus.UsedUp,
            PunchCardUsageRecordStatus.Expired => PunchCardInstanceStatus.Expired,
            _ => PunchCardInstanceStatus.Active
        };

        if (isNewInstance)
        {
            await repository.AddInstanceAsync(instance, cancellationToken);
            await repository.SaveChangesAsync(cancellationToken);
        }
        else
        {
            await repository.UpdateInstanceAsync(instance, cancellationToken);
        }

        var record = new PunchCardUsageRecord
        {
            StoreId = request.StoreId,
            PunchCardTemplateId = template.Id,
            PunchCardInstanceId = instance.Id,
            RecordNo = PunchCardDtoFactory.GenerateRecordNo(usedAt),
            ProductName = productName,
            UsedAt = usedAt,
            UsedTimes = usedTimes,
            RemainingTimesAfterUse = remainingTimes,
            StatusAfterUse = statusAfterUse,
            ExtraPayAmount = extraPayAmount
        };

        await repository.AddUsageRecordAsync(record, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);

        return PunchCardDtoFactory.ToUsageRecordDto(record, instance, template, usedAt);
    }
}
