using System.Text.Json;
using TakeoutSaaS.Application.App.Coupons.FullReduction.Dto;
using TakeoutSaaS.Domain.Coupons.Entities;
using TakeoutSaaS.Domain.Coupons.Enums;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;

namespace TakeoutSaaS.Application.App.Coupons.FullReduction;

/// <summary>
/// 满减活动映射辅助。
/// </summary>
internal static class FullReductionMapping
{
    private const string ActivityTypeGift = "gift";
    private const string ActivityTypeReduce = "reduce";
    private const string ActivityTypeSecondHalf = "second_half";

    private const string DiscountTypeFree = "free";
    private const string DiscountTypeHalf = "half";
    private const string DiscountTypeSeventy = "seventy";
    private const string DiscountTypeSixty = "sixty";

    private const string DisplayStatusEnded = "ended";
    private const string DisplayStatusOngoing = "ongoing";
    private const string DisplayStatusUpcoming = "upcoming";

    private const string GiftScopeSameLowest = "same_lowest";
    private const string GiftScopeSpecified = "specified";

    private const string ScopeTypeAll = "all";
    private const string ScopeTypeCategory = "category";
    private const string ScopeTypeProduct = "product";

    private static readonly HashSet<string> AllowedActivityTypes =
    [
        ActivityTypeReduce,
        ActivityTypeGift,
        ActivityTypeSecondHalf
    ];

    private static readonly HashSet<string> AllowedChannels =
    [
        "delivery",
        "pickup",
        "dine_in"
    ];

    private static readonly HashSet<string> AllowedDiscountTypes =
    [
        DiscountTypeHalf,
        DiscountTypeSixty,
        DiscountTypeSeventy,
        DiscountTypeFree
    ];

    private static readonly HashSet<string> AllowedDisplayStatuses =
    [
        DisplayStatusOngoing,
        DisplayStatusUpcoming,
        DisplayStatusEnded
    ];

    private static readonly HashSet<string> AllowedStoreScopeModes =
    [
        "all",
        "stores"
    ];

    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true
    };

    /// <summary>
    /// 校验并标准化活动类型筛选。
    /// </summary>
    public static bool TryNormalizeActivityTypeFilter(string? value, out string? normalized)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        if (string.IsNullOrWhiteSpace(candidate))
        {
            normalized = null;
            return true;
        }

        if (AllowedActivityTypes.Contains(candidate))
        {
            normalized = candidate;
            return true;
        }

        normalized = null;
        return false;
    }

    /// <summary>
    /// 校验并标准化展示状态筛选。
    /// </summary>
    public static bool TryNormalizeDisplayStatusFilter(string? value, out string? normalized)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        if (string.IsNullOrWhiteSpace(candidate))
        {
            normalized = null;
            return true;
        }

        if (AllowedDisplayStatuses.Contains(candidate))
        {
            normalized = candidate;
            return true;
        }

        normalized = null;
        return false;
    }

    /// <summary>
    /// 解析状态文本为领域状态。
    /// </summary>
    public static PromotionStatus ParseStatus(string? value)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        return candidate switch
        {
            "active" => PromotionStatus.Active,
            "completed" => PromotionStatus.Completed,
            _ => throw new BusinessException(ErrorCodes.BadRequest, "status 参数不合法")
        };
    }

    /// <summary>
    /// 输出状态文本。
    /// </summary>
    public static string ToStatusText(PromotionStatus status)
    {
        return status switch
        {
            PromotionStatus.Active => "active",
            PromotionStatus.Completed => "completed",
            _ => "completed"
        };
    }

    /// <summary>
    /// 解析活动规则。
    /// </summary>
    public static FullReductionRulesDto DeserializeRules(string? rulesJson, long campaignId)
    {
        if (string.IsNullOrWhiteSpace(rulesJson))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"活动[{campaignId}]规则缺失");
        }

        var payload = JsonSerializer.Deserialize<FullReductionRulesDto>(rulesJson, JsonOptions)
            ?? throw new BusinessException(ErrorCodes.BadRequest, $"活动[{campaignId}]规则格式错误");

        return NormalizeRulesForSave(
            payload.ActivityType,
            payload.ReduceTiers,
            payload.GiftRule,
            payload.SecondHalfRule,
            payload.Channels,
            payload.StoreScopeMode,
            payload.StoreIds,
            payload.ScopeStoreId,
            payload.StackWithCoupon,
            payload.Description,
            payload.Metrics,
            null);
    }

    /// <summary>
    /// 序列化活动规则。
    /// </summary>
    public static string SerializeRules(FullReductionRulesDto rules)
    {
        return JsonSerializer.Serialize(rules, JsonOptions);
    }

    /// <summary>
    /// 标准化并校验保存规则。
    /// </summary>
    public static FullReductionRulesDto NormalizeRulesForSave(
        string? activityType,
        IReadOnlyCollection<FullReductionTierRuleDto>? reduceTiers,
        FullReductionGiftRuleDto? giftRule,
        FullReductionSecondHalfRuleDto? secondHalfRule,
        IReadOnlyCollection<string>? channels,
        string? storeScopeMode,
        IReadOnlyCollection<long>? storeIds,
        long scopeStoreId,
        bool stackWithCoupon,
        string? description,
        FullReductionMetricsDto? metrics,
        FullReductionMetricsDto? fallbackMetrics)
    {
        var normalizedActivityType = NormalizeActivityType(activityType);
        var normalizedChannels = NormalizeChannels(channels);
        var normalizedStoreScopeMode = NormalizeStoreScopeMode(storeScopeMode);
        var normalizedStoreIds = NormalizeStoreIds(storeIds);

        if (scopeStoreId <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "scopeStoreId 参数不合法");
        }

        if (!normalizedStoreIds.Contains(scopeStoreId))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "scopeStoreId 必须在活动门店范围内");
        }

        var normalizedDescription = NormalizeDescription(description);
        var normalizedMetrics = NormalizeMetrics(metrics ?? fallbackMetrics);

        return normalizedActivityType switch
        {
            ActivityTypeReduce => new FullReductionRulesDto
            {
                ActivityType = ActivityTypeReduce,
                ReduceTiers = NormalizeReduceTiers(reduceTiers),
                GiftRule = null,
                SecondHalfRule = null,
                Channels = normalizedChannels,
                StoreScopeMode = normalizedStoreScopeMode,
                StoreIds = normalizedStoreIds,
                ScopeStoreId = scopeStoreId,
                StackWithCoupon = stackWithCoupon,
                Description = normalizedDescription,
                Metrics = normalizedMetrics
            },
            ActivityTypeGift => new FullReductionRulesDto
            {
                ActivityType = ActivityTypeGift,
                ReduceTiers = [],
                GiftRule = NormalizeGiftRule(giftRule),
                SecondHalfRule = null,
                Channels = normalizedChannels,
                StoreScopeMode = normalizedStoreScopeMode,
                StoreIds = normalizedStoreIds,
                ScopeStoreId = scopeStoreId,
                StackWithCoupon = stackWithCoupon,
                Description = normalizedDescription,
                Metrics = normalizedMetrics
            },
            ActivityTypeSecondHalf => new FullReductionRulesDto
            {
                ActivityType = ActivityTypeSecondHalf,
                ReduceTiers = [],
                GiftRule = null,
                SecondHalfRule = NormalizeSecondHalfRule(secondHalfRule),
                Channels = normalizedChannels,
                StoreScopeMode = normalizedStoreScopeMode,
                StoreIds = normalizedStoreIds,
                ScopeStoreId = scopeStoreId,
                StackWithCoupon = stackWithCoupon,
                Description = normalizedDescription,
                Metrics = normalizedMetrics
            },
            _ => throw new BusinessException(ErrorCodes.BadRequest, "activityType 参数不合法")
        };
    }

    /// <summary>
    /// 判断活动是否对可见门店集合可见。
    /// </summary>
    public static bool HasVisibleStore(FullReductionRulesDto rules, IReadOnlyCollection<long> visibleStoreIds)
    {
        if (visibleStoreIds.Count == 0)
        {
            return false;
        }

        return rules.StoreIds.Any(visibleStoreIds.Contains);
    }

    /// <summary>
    /// 解析卡片展示状态。
    /// </summary>
    public static string ResolveDisplayStatus(PromotionCampaign campaign, DateTime nowUtc)
    {
        if (campaign.Status == PromotionStatus.Completed || campaign.Status == PromotionStatus.Paused)
        {
            return DisplayStatusEnded;
        }

        if (nowUtc < campaign.StartAt)
        {
            return DisplayStatusUpcoming;
        }

        if (nowUtc > campaign.EndAt)
        {
            return DisplayStatusEnded;
        }

        return DisplayStatusOngoing;
    }

    /// <summary>
    /// 判断是否弱化展示。
    /// </summary>
    public static bool IsDimmed(string displayStatus)
    {
        return string.Equals(displayStatus, DisplayStatusEnded, StringComparison.Ordinal);
    }

    /// <summary>
    /// 将结束时间规范为当天 23:59:59.9999999。
    /// </summary>
    public static DateTime NormalizeEndOfDay(DateTime dateTime)
    {
        var utc = dateTime.Kind == DateTimeKind.Utc
            ? dateTime
            : DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
        return utc.Date.AddDays(1).AddTicks(-1);
    }

    /// <summary>
    /// 将开始时间规范为当天 00:00:00。
    /// </summary>
    public static DateTime NormalizeStartOfDay(DateTime dateTime)
    {
        var utc = dateTime.Kind == DateTimeKind.Utc
            ? dateTime
            : DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
        return utc.Date;
    }

    private static string NormalizeActivityType(string? value)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        if (!AllowedActivityTypes.Contains(candidate))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "activityType 参数不合法");
        }

        return candidate;
    }

    private static string NormalizeStoreScopeMode(string? value)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        if (!AllowedStoreScopeModes.Contains(candidate))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "storeScopeMode 参数不合法");
        }

        return candidate;
    }

    private static IReadOnlyList<long> NormalizeStoreIds(IReadOnlyCollection<long>? storeIds)
    {
        var normalized = (storeIds ?? Array.Empty<long>())
            .Where(id => id > 0)
            .Distinct()
            .OrderBy(id => id)
            .ToList();

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

        return normalized;
    }

    private static IReadOnlyList<string> NormalizeChannels(IReadOnlyCollection<string>? channels)
    {
        var normalized = (channels ?? Array.Empty<string>())
            .Select(item => (item ?? string.Empty).Trim().ToLowerInvariant())
            .Where(item => !string.IsNullOrWhiteSpace(item))
            .Distinct()
            .ToList();

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

        if (normalized.Any(item => !AllowedChannels.Contains(item)))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "channels 存在非法值");
        }

        return normalized;
    }

    private static string? NormalizeDescription(string? description)
    {
        var normalized = (description ?? string.Empty).Trim();
        if (normalized.Length == 0)
        {
            return null;
        }

        if (normalized.Length > 512)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "description 长度不能超过 512");
        }

        return normalized;
    }

    private static IReadOnlyList<FullReductionTierRuleDto> NormalizeReduceTiers(IReadOnlyCollection<FullReductionTierRuleDto>? tiers)
    {
        var normalized = (tiers ?? Array.Empty<FullReductionTierRuleDto>())
            .Select(item => new FullReductionTierRuleDto
            {
                MeetAmount = NormalizeMoney(item.MeetAmount, "满减门槛金额必须大于 0"),
                ReduceAmount = NormalizeMoney(item.ReduceAmount, "满减优惠金额必须大于 0")
            })
            .OrderBy(item => item.MeetAmount)
            .ToList();

        if (normalized.Count == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "满减规则不能为空");
        }

        for (var index = 0; index < normalized.Count; index++)
        {
            var current = normalized[index];
            if (current.ReduceAmount >= current.MeetAmount)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "满减优惠金额必须小于门槛金额");
            }

            if (index == 0)
            {
                continue;
            }

            var previous = normalized[index - 1];
            if (current.MeetAmount <= previous.MeetAmount)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "满减门槛金额必须严格递增");
            }
        }

        return normalized;
    }

    private static FullReductionGiftRuleDto NormalizeGiftRule(FullReductionGiftRuleDto? giftRule)
    {
        if (giftRule is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "giftRule 不能为空");
        }

        if (giftRule.BuyQuantity <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "购买数量门槛必须大于 0");
        }

        if (giftRule.GiftQuantity <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "赠送数量必须大于 0");
        }

        var normalizedGiftScopeType = (giftRule.GiftScopeType ?? string.Empty).Trim().ToLowerInvariant();
        if (normalizedGiftScopeType is not (GiftScopeSameLowest or GiftScopeSpecified))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "giftScopeType 参数不合法");
        }

        var normalizedApplicableScope = NormalizeScope(giftRule.ApplicableScope, true, "适用商品范围");
        var normalizedGiftScope = normalizedGiftScopeType == GiftScopeSpecified
            ? NormalizeScope(giftRule.GiftScope, false, "指定赠品范围")
            : new FullReductionScopeRuleDto
            {
                ScopeType = ScopeTypeAll,
                CategoryIds = [],
                ProductIds = []
            };

        return new FullReductionGiftRuleDto
        {
            BuyQuantity = giftRule.BuyQuantity,
            GiftQuantity = giftRule.GiftQuantity,
            GiftScopeType = normalizedGiftScopeType,
            ApplicableScope = normalizedApplicableScope,
            GiftScope = normalizedGiftScope
        };
    }

    private static FullReductionSecondHalfRuleDto NormalizeSecondHalfRule(FullReductionSecondHalfRuleDto? secondHalfRule)
    {
        if (secondHalfRule is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "secondHalfRule 不能为空");
        }

        var normalizedDiscountType = (secondHalfRule.DiscountType ?? string.Empty).Trim().ToLowerInvariant();
        if (!AllowedDiscountTypes.Contains(normalizedDiscountType))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "discountType 参数不合法");
        }

        var normalizedApplicableScope = NormalizeScope(secondHalfRule.ApplicableScope, false, "第二份半价适用范围");

        return new FullReductionSecondHalfRuleDto
        {
            DiscountType = normalizedDiscountType,
            ApplicableScope = normalizedApplicableScope
        };
    }

    private static FullReductionScopeRuleDto NormalizeScope(
        FullReductionScopeRuleDto? scope,
        bool allowAll,
        string scopeName)
    {
        if (scope is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{scopeName}不能为空");
        }

        var normalizedScopeType = (scope.ScopeType ?? string.Empty).Trim().ToLowerInvariant();
        if (normalizedScopeType is not (ScopeTypeAll or ScopeTypeCategory or ScopeTypeProduct))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{scopeName}类型不合法");
        }

        if (!allowAll && normalizedScopeType == ScopeTypeAll)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{scopeName}不支持全部商品");
        }

        var normalizedCategoryIds = scope.CategoryIds
            .Where(id => id > 0)
            .Distinct()
            .OrderBy(id => id)
            .ToList();

        var normalizedProductIds = scope.ProductIds
            .Where(id => id > 0)
            .Distinct()
            .OrderBy(id => id)
            .ToList();

        if (normalizedScopeType == ScopeTypeCategory && normalizedCategoryIds.Count == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{scopeName}缺少分类");
        }

        if (normalizedScopeType == ScopeTypeProduct && normalizedProductIds.Count == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{scopeName}缺少商品");
        }

        if (normalizedScopeType == ScopeTypeAll)
        {
            normalizedCategoryIds = [];
            normalizedProductIds = [];
        }

        return new FullReductionScopeRuleDto
        {
            ScopeType = normalizedScopeType,
            CategoryIds = normalizedCategoryIds,
            ProductIds = normalizedProductIds
        };
    }

    private static FullReductionMetricsDto NormalizeMetrics(FullReductionMetricsDto? metrics)
    {
        if (metrics is null)
        {
            return new FullReductionMetricsDto();
        }

        return new FullReductionMetricsDto
        {
            ParticipatingOrderCount = Math.Max(0, metrics.ParticipatingOrderCount),
            DiscountTotalAmount = NormalizeNonNegativeMoney(metrics.DiscountTotalAmount),
            TicketIncreaseAmount = NormalizeNonNegativeMoney(metrics.TicketIncreaseAmount),
            GiftedCount = Math.Max(0, metrics.GiftedCount),
            DrivenSalesAmount = NormalizeNonNegativeMoney(metrics.DrivenSalesAmount),
            AttachRateIncreasePercent = NormalizeNonNegativeMoney(metrics.AttachRateIncreasePercent),
            MonthlyDrivenSalesAmount = NormalizeNonNegativeMoney(metrics.MonthlyDrivenSalesAmount),
            AverageTicketIncrease = NormalizeNonNegativeMoney(metrics.AverageTicketIncrease)
        };
    }

    private static decimal NormalizeMoney(decimal value, string errorMessage)
    {
        if (value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, errorMessage);
        }

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

    private static decimal NormalizeNonNegativeMoney(decimal value)
    {
        if (value <= 0)
        {
            return 0m;
        }

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