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

namespace TakeoutSaaS.Application.App.Coupons.Seckill;

/// <summary>
/// 秒杀映射与规则校验辅助。
/// </summary>
internal static class SeckillMapping
{
    private const string ActivityTypeTimed = "timed";
    private const string ActivityTypeHourly = "hourly";

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

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

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

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

    /// <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 string ToProductStatusText(ProductStatus status, ProductSoldoutMode? soldoutMode)
    {
        if (soldoutMode.HasValue)
        {
            return "sold_out";
        }

        return status switch
        {
            ProductStatus.OnSale => "on_sale",
            _ => "off_shelf"
        };
    }

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

    /// <summary>
    /// 将结束时间规范为当天 23:59:59.9999999（UTC）。
    /// </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>
    /// 解析活动规则。
    /// </summary>
    public static SeckillRulesDto DeserializeRules(string? rulesJson, long campaignId)
    {
        if (string.IsNullOrWhiteSpace(rulesJson))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"活动[{campaignId}]规则缺失");
        }

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

        return NormalizeRulesForSave(
            payload.ActivityType,
            payload.StartDate,
            payload.EndDate,
            payload.TimeStart,
            payload.TimeEnd,
            payload.Sessions,
            payload.Channels,
            payload.PerUserLimit,
            payload.PreheatEnabled,
            payload.PreheatHours,
            payload.StoreIds,
            payload.Products,
            payload.Metrics,
            null);
    }

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

    /// <summary>
    /// 标准化并校验保存规则。
    /// </summary>
    public static SeckillRulesDto NormalizeRulesForSave(
        string? activityType,
        DateTime? startDate,
        DateTime? endDate,
        TimeSpan? timeStart,
        TimeSpan? timeEnd,
        IReadOnlyCollection<SeckillSessionRuleDto>? sessions,
        IReadOnlyCollection<string>? channels,
        int? perUserLimit,
        bool preheatEnabled,
        int? preheatHours,
        IReadOnlyCollection<long>? storeIds,
        IReadOnlyCollection<SeckillProductRuleDto>? products,
        SeckillMetricsDto? metrics,
        SeckillMetricsDto? fallbackMetrics)
    {
        var normalizedActivityType = NormalizeActivityType(activityType);

        var (normalizedStartDate, normalizedEndDate) = NormalizeDateRange(
            normalizedActivityType,
            startDate,
            endDate);
        var (normalizedTimeStart, normalizedTimeEnd) = NormalizeTimeRange(
            normalizedActivityType,
            timeStart,
            timeEnd);

        var normalizedSessions = NormalizeSessions(normalizedActivityType, sessions);
        var normalizedChannels = NormalizeChannels(channels);
        var normalizedPerUserLimit = NormalizeOptionalLimit(perUserLimit, "perUserLimit 参数不合法");
        var normalizedPreheatHours = NormalizePreheat(preheatEnabled, preheatHours);
        var normalizedStoreIds = NormalizeStoreIds(storeIds);
        var normalizedProducts = NormalizeProducts(products, normalizedPerUserLimit);
        var normalizedMetrics = NormalizeMetrics(metrics, fallbackMetrics);

        return new SeckillRulesDto
        {
            ActivityType = normalizedActivityType,
            StartDate = normalizedStartDate,
            EndDate = normalizedEndDate,
            TimeStart = normalizedTimeStart,
            TimeEnd = normalizedTimeEnd,
            Sessions = normalizedSessions,
            Channels = normalizedChannels,
            PerUserLimit = normalizedPerUserLimit,
            PreheatEnabled = preheatEnabled,
            PreheatHours = normalizedPreheatHours,
            StoreIds = normalizedStoreIds,
            Products = normalizedProducts,
            Metrics = normalizedMetrics
        };
    }

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

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

    /// <summary>
    /// 解析活动展示状态。
    /// </summary>
    public static string ResolveDisplayStatus(
        PromotionCampaign campaign,
        SeckillRulesDto rules,
        DateTime nowUtc)
    {
        if (campaign.Status is PromotionStatus.Completed or PromotionStatus.Paused)
        {
            return DisplayStatusEnded;
        }

        if (string.Equals(rules.ActivityType, ActivityTypeHourly, StringComparison.Ordinal))
        {
            return DisplayStatusOngoing;
        }

        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>
    /// 计算活动实体起止时间窗口。
    /// </summary>
    public static (DateTime StartAt, DateTime EndAt) ResolveCampaignWindow(SeckillRulesDto rules, DateTime nowUtc)
    {
        if (string.Equals(rules.ActivityType, ActivityTypeHourly, StringComparison.Ordinal))
        {
            var defaultStart = NormalizeStartOfDay(nowUtc);
            var defaultEnd = NormalizeEndOfDay(defaultStart.AddYears(20));
            return (defaultStart, defaultEnd);
        }

        if (!rules.StartDate.HasValue || !rules.EndDate.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "活动日期范围缺失");
        }

        return (NormalizeStartOfDay(rules.StartDate.Value), NormalizeEndOfDay(rules.EndDate.Value));
    }

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

        return candidate;
    }

    private static (DateTime? StartDate, DateTime? EndDate) NormalizeDateRange(
        string activityType,
        DateTime? startDate,
        DateTime? endDate)
    {
        if (string.Equals(activityType, ActivityTypeHourly, StringComparison.Ordinal))
        {
            if (startDate.HasValue || endDate.HasValue)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "整点秒杀不支持日期范围");
            }

            return (null, null);
        }

        if (!startDate.HasValue || !endDate.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "startDate 与 endDate 不能为空");
        }

        var normalizedStart = NormalizeStartOfDay(startDate.Value);
        var normalizedEnd = NormalizeStartOfDay(endDate.Value);

        if (normalizedStart > normalizedEnd)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "活动开始日期不能晚于结束日期");
        }

        return (normalizedStart, normalizedEnd);
    }

    private static (TimeSpan? TimeStart, TimeSpan? TimeEnd) NormalizeTimeRange(
        string activityType,
        TimeSpan? timeStart,
        TimeSpan? timeEnd)
    {
        if (string.Equals(activityType, ActivityTypeHourly, StringComparison.Ordinal))
        {
            if (timeStart.HasValue || timeEnd.HasValue)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "整点秒杀不支持每日时段");
            }

            return (null, null);
        }

        if (!timeStart.HasValue || !timeEnd.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "timeStart 与 timeEnd 不能为空");
        }

        if (timeStart.Value >= timeEnd.Value)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "每日开始时间必须早于结束时间");
        }

        return (timeStart.Value, timeEnd.Value);
    }

    private static IReadOnlyList<SeckillSessionRuleDto> NormalizeSessions(
        string activityType,
        IReadOnlyCollection<SeckillSessionRuleDto>? sessions)
    {
        if (string.Equals(activityType, ActivityTypeTimed, StringComparison.Ordinal))
        {
            return [];
        }

        var normalized = (sessions ?? Array.Empty<SeckillSessionRuleDto>())
            .Select(item =>
            {
                var startTime = NormalizeSessionStartTime(item.StartTime);
                var durationMinutes = NormalizeRequiredPositiveInt(item.DurationMinutes, "场次持续时长必须大于 0");

                return new SeckillSessionRuleDto
                {
                    StartTime = startTime,
                    DurationMinutes = durationMinutes
                };
            })
            .OrderBy(item => item.StartTime)
            .ToList();

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

        var duplicateStartTime = normalized
            .GroupBy(item => item.StartTime)
            .FirstOrDefault(group => group.Count() > 1)?
            .Key;
        if (duplicateStartTime.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"场次时间重复: {duplicateStartTime.Value:hh\\:mm}");
        }

        return normalized;
    }

    private static TimeSpan NormalizeSessionStartTime(TimeSpan value)
    {
        if (value < TimeSpan.Zero || value >= TimeSpan.FromDays(1))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "场次时间不合法");
        }

        return new TimeSpan(value.Hours, value.Minutes, 0);
    }

    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 int? NormalizePreheat(bool preheatEnabled, int? preheatHours)
    {
        if (!preheatEnabled)
        {
            return null;
        }

        if (!preheatHours.HasValue || preheatHours.Value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "preheatHours 必须大于 0");
        }

        return preheatHours.Value;
    }

    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<SeckillProductRuleDto> NormalizeProducts(
        IReadOnlyCollection<SeckillProductRuleDto>? products,
        int? campaignPerUserLimit)
    {
        var normalized = (products ?? Array.Empty<SeckillProductRuleDto>())
            .Select(item =>
            {
                if (item.ProductId <= 0)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "productId 参数不合法");
                }

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

                var normalizedName = (item.Name ?? string.Empty).Trim();
                if (string.IsNullOrWhiteSpace(normalizedName))
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "商品名称不能为空");
                }

                var normalizedSpuCode = (item.SpuCode ?? string.Empty).Trim();
                if (string.IsNullOrWhiteSpace(normalizedSpuCode))
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "商品 SPU 编码不能为空");
                }

                var normalizedStatus = NormalizeProductStatus(item.Status);
                var normalizedOriginalPrice = NormalizeMoney(item.OriginalPrice, "商品原价必须大于 0");
                var normalizedSeckillPrice = NormalizeMoney(item.SeckillPrice, "秒杀价必须大于 0");
                if (normalizedSeckillPrice > normalizedOriginalPrice)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "秒杀价不能高于原价");
                }

                var normalizedStockLimit = NormalizeRequiredPositiveInt(item.StockLimit, "stockLimit 必须大于 0");
                var normalizedSoldCount = Math.Max(0, item.SoldCount);
                if (normalizedSoldCount > normalizedStockLimit)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "已售数量不能大于限量库存");
                }

                var normalizedPerUserLimit = NormalizeOptionalLimit(item.PerUserLimit, "商品每人限购必须大于 0");
                if (campaignPerUserLimit.HasValue &&
                    normalizedPerUserLimit.HasValue &&
                    normalizedPerUserLimit.Value > campaignPerUserLimit.Value)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "商品每人限购不能大于活动每人限购");
                }

                if (normalizedSoldCount >= normalizedStockLimit)
                {
                    normalizedStatus = "sold_out";
                }

                return new SeckillProductRuleDto
                {
                    ProductId = item.ProductId,
                    CategoryId = item.CategoryId,
                    CategoryName = (item.CategoryName ?? string.Empty).Trim(),
                    Name = normalizedName,
                    SpuCode = normalizedSpuCode,
                    Status = normalizedStatus,
                    OriginalPrice = normalizedOriginalPrice,
                    SeckillPrice = normalizedSeckillPrice,
                    StockLimit = normalizedStockLimit,
                    PerUserLimit = normalizedPerUserLimit,
                    SoldCount = normalizedSoldCount
                };
            })
            .OrderBy(item => item.Name)
            .ThenBy(item => item.ProductId)
            .ToList();

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

        var duplicateProductId = normalized
            .GroupBy(item => item.ProductId)
            .FirstOrDefault(group => group.Count() > 1)?
            .Key;
        if (duplicateProductId.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"商品重复: {duplicateProductId.Value}");
        }

        return normalized;
    }

    private static string NormalizeProductStatus(string? value)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        return candidate switch
        {
            "on_sale" => "on_sale",
            "off_shelf" => "off_shelf",
            "sold_out" => "sold_out",
            _ => throw new BusinessException(ErrorCodes.BadRequest, "商品状态不合法")
        };
    }

    private static int? NormalizeOptionalLimit(int? value, string errorMessage)
    {
        if (!value.HasValue)
        {
            return null;
        }

        if (value.Value <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, errorMessage);
        }

        return value.Value;
    }

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

        return value;
    }

    private static SeckillMetricsDto NormalizeMetrics(SeckillMetricsDto? metrics, SeckillMetricsDto? fallbackMetrics)
    {
        var source = metrics ?? fallbackMetrics;
        if (source is null)
        {
            return new SeckillMetricsDto();
        }

        var participantCount = Math.Max(0, source.ParticipantCount);
        var dealCount = Math.Max(0, source.DealCount);
        var conversionRate = ClampPercent(source.ConversionRate);

        if (conversionRate <= 0m && participantCount > 0)
        {
            conversionRate = decimal.Round(
                dealCount * 100m / participantCount,
                2,
                MidpointRounding.AwayFromZero);
        }

        return new SeckillMetricsDto
        {
            ParticipantCount = participantCount,
            DealCount = dealCount,
            ConversionRate = conversionRate,
            MonthlySeckillSalesCount = Math.Max(0, source.MonthlySeckillSalesCount)
        };
    }

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

        if (value >= 100)
        {
            return 100m;
        }

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

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

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