using MediatR;
using TakeoutSaaS.Application.App.Coupons.Commands;
using TakeoutSaaS.Application.App.Coupons.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.Handlers;

/// <summary>
/// 保存优惠券模板命令处理器。
/// </summary>
public sealed class SaveCouponTemplateCommandHandler(
    ICouponRepository couponRepository,
    ITenantProvider tenantProvider)
    : IRequestHandler<SaveCouponTemplateCommand, CouponTemplateDetailDto>
{
    /// <inheritdoc />
    public async Task<CouponTemplateDetailDto> Handle(SaveCouponTemplateCommand request, CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        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 (!CouponTemplateMapping.TryParseCouponType(request.CouponType, out var couponType))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "couponType 参数不合法");
        }

        if (!CouponTemplateMapping.TryParseEditorStatus(request.Status, out var status))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "status 参数不合法");
        }

        if (request.TotalQuantity <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "发放总量必须大于 0");
        }

        if (request.PerUserLimit.HasValue && request.PerUserLimit.Value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "每人限领必须大于 0");
        }

        var normalizedChannels = CouponTemplateMapping.NormalizeChannelsForSave(request.Channels);
        var storeScopeJson = CouponTemplateMapping.SerializeStoreScope(request.StoreScopeMode, request.StoreScopeStoreIds);
        var channelsJson = CouponTemplateMapping.SerializeChannels(normalizedChannels);

        var (value, minimumSpend, validFrom, validTo, relativeValidDays) = NormalizeBusinessFields(request, couponType);

        CouponTemplate template;
        if (!request.TemplateId.HasValue)
        {
            template = new CouponTemplate
            {
                Name = normalizedName,
                CouponType = couponType,
                Value = value,
                MinimumSpend = minimumSpend,
                ValidFrom = validFrom,
                ValidTo = validTo,
                RelativeValidDays = relativeValidDays,
                TotalQuantity = request.TotalQuantity,
                ClaimedQuantity = 0,
                PerUserLimit = request.PerUserLimit,
                StoreScopeJson = storeScopeJson,
                ProductScopeJson = null,
                ChannelsJson = channelsJson,
                AllowStack = false,
                Status = status,
                Description = null
            };

            await couponRepository.AddTemplateAsync(template, cancellationToken);
            await couponRepository.SaveChangesAsync(cancellationToken);
        }
        else
        {
            template = await couponRepository.FindTemplateByIdAsync(request.TemplateId.Value, tenantId, cancellationToken)
                ?? throw new BusinessException(ErrorCodes.NotFound, "优惠券不存在");

            var existingScope = CouponTemplateMapping.DeserializeStoreScope(template.StoreScopeJson, template.Id);
            if (!existingScope.StoreIds.Contains(request.StoreId) && !string.Equals(existingScope.Mode, "all", StringComparison.Ordinal))
            {
                throw new BusinessException(ErrorCodes.NotFound, "优惠券不存在");
            }

            if (request.TotalQuantity < template.ClaimedQuantity)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "发放总量不能小于已领取数量");
            }

            if (template.TotalQuantity.HasValue && request.TotalQuantity < template.TotalQuantity.Value)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "发放总量不可减少，只能增加");
            }

            template.Name = normalizedName;
            template.CouponType = couponType;
            template.Value = value;
            template.MinimumSpend = minimumSpend;
            template.ValidFrom = validFrom;
            template.ValidTo = validTo;
            template.RelativeValidDays = relativeValidDays;
            template.TotalQuantity = request.TotalQuantity;
            template.PerUserLimit = request.PerUserLimit;
            template.StoreScopeJson = storeScopeJson;
            template.ChannelsJson = channelsJson;
            template.Status = status;

            await couponRepository.UpdateTemplateAsync(template, cancellationToken);
            await couponRepository.SaveChangesAsync(cancellationToken);
        }

        var storeScope = CouponTemplateMapping.DeserializeStoreScope(template.StoreScopeJson, template.Id);
        var channels = CouponTemplateMapping.DeserializeChannels(template.ChannelsJson, template.Id);
        return CouponTemplateDtoFactory.ToDetailDto(template, storeScope, channels);
    }

    private static (decimal Value, decimal? MinimumSpend, DateTime? ValidFrom, DateTime? ValidTo, int? RelativeValidDays)
        NormalizeBusinessFields(SaveCouponTemplateCommand request, CouponType couponType)
    {
        var value = couponType switch
        {
            CouponType.AmountOff => NormalizeAmountOffValue(request.Value),
            CouponType.Percentage => NormalizeDiscountValue(request.Value),
            CouponType.DeliveryFee => 0m,
            _ => throw new BusinessException(ErrorCodes.BadRequest, "不支持的券类型")
        };

        var minimumSpend = couponType switch
        {
            CouponType.AmountOff => NormalizeRequiredMinimumSpend(request.MinimumSpend),
            CouponType.Percentage => NormalizeOptionalMinimumSpend(request.MinimumSpend),
            CouponType.DeliveryFee => null,
            _ => null
        };

        if (couponType == CouponType.DeliveryFee && request.MinimumSpend.HasValue && request.MinimumSpend.Value > 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "免配送费券不支持设置使用门槛");
        }

        var validityType = (request.ValidityType ?? string.Empty).Trim().ToLowerInvariant();
        return validityType switch
        {
            "fixed" => NormalizeFixedValidity(value, minimumSpend, request.ValidFrom, request.ValidTo),
            "days" => (
                value,
                minimumSpend,
                null,
                null,
                NormalizeRelativeValidDays(request.RelativeValidDays)),
            _ => throw new BusinessException(ErrorCodes.BadRequest, "validityType 参数不合法")
        };
    }

    private static decimal NormalizeAmountOffValue(decimal value)
    {
        if (value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "满减券面额必须大于 0");
        }

        return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
    }

    private static decimal NormalizeDiscountValue(decimal value)
    {
        if (value <= 0 || value >= 10)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "折扣券面额必须在 0 到 10 之间");
        }

        return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
    }

    private static decimal NormalizeRequiredMinimumSpend(decimal? value)
    {
        if (!value.HasValue || value.Value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "满减券必须设置使用门槛");
        }

        return decimal.Round(value.Value, 2, MidpointRounding.AwayFromZero);
    }

    private static decimal? NormalizeOptionalMinimumSpend(decimal? value)
    {
        if (!value.HasValue)
        {
            return null;
        }

        if (value.Value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "使用门槛必须大于 0");
        }

        return decimal.Round(value.Value, 2, MidpointRounding.AwayFromZero);
    }

    private static DateTime NormalizeDateStart(DateTime? value, string fieldName)
    {
        if (!value.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 不能为空");
        }

        var utc = value.Value.Kind == DateTimeKind.Utc
            ? value.Value
            : DateTime.SpecifyKind(value.Value, DateTimeKind.Utc);
        return utc.Date;
    }

    private static DateTime NormalizeDateEnd(DateTime? value, string fieldName)
    {
        if (!value.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 不能为空");
        }

        var utc = value.Value.Kind == DateTimeKind.Utc
            ? value.Value
            : DateTime.SpecifyKind(value.Value, DateTimeKind.Utc);
        return utc.Date.AddDays(1).AddTicks(-1);
    }

    private static int NormalizeRelativeValidDays(int? value)
    {
        if (!value.HasValue || value.Value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "领取后有效天数必须大于 0");
        }

        return value.Value;
    }

    private static (decimal Value, decimal? MinimumSpend, DateTime? ValidFrom, DateTime? ValidTo, int? RelativeValidDays)
        NormalizeFixedValidity(decimal value, decimal? minimumSpend, DateTime? validFrom, DateTime? validTo)
    {
        var normalizedStart = NormalizeDateStart(validFrom, "validFrom");
        var normalizedEnd = NormalizeDateEnd(validTo, "validTo");
        if (normalizedStart > normalizedEnd)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "固定有效期开始时间不能晚于结束时间");
        }

        return (value, minimumSpend, normalizedStart, normalizedEnd, null);
    }
}
