using System.Text.Json;
using TakeoutSaaS.Application.App.Coupons.FlashSale.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.FlashSale;

/// <summary>
/// 限时折扣映射与规则校验辅助。
/// </summary>
internal static class FlashSaleMapping
{
    private const string CycleTypeOnce = "once";
    private const string CycleTypeRecurring = "recurring";

    private const string RecurringDateModeFixed = "fixed";
    private const string RecurringDateModeLongTerm = "long_term";

    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 FlashSaleRulesDto DeserializeRules(string? rulesJson, long campaignId)
    {
        if (string.IsNullOrWhiteSpace(rulesJson))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"活动[{campaignId}]规则缺失");
        }

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

        return NormalizeRulesForSave(
            payload.CycleType,
            payload.RecurringDateMode,
            payload.StartDate,
            payload.EndDate,
            payload.TimeStart,
            payload.TimeEnd,
            payload.WeekDays,
            payload.Channels,
            payload.PerUserLimit,
            payload.StoreIds,
            payload.Products,
            payload.Metrics,
            null);
    }

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

    /// <summary>
    /// 标准化并校验保存规则。
    /// </summary>
    public static FlashSaleRulesDto NormalizeRulesForSave(
        string? cycleType,
        string? recurringDateMode,
        DateTime? startDate,
        DateTime? endDate,
        TimeSpan? timeStart,
        TimeSpan? timeEnd,
        IReadOnlyCollection<int>? weekDays,
        IReadOnlyCollection<string>? channels,
        int? perUserLimit,
        IReadOnlyCollection<long>? storeIds,
        IReadOnlyCollection<FlashSaleProductRuleDto>? products,
        FlashSaleMetricsDto? metrics,
        FlashSaleMetricsDto? fallbackMetrics)
    {
        var normalizedCycleType = NormalizeCycleType(cycleType);
        var normalizedRecurringDateMode = NormalizeRecurringDateMode(recurringDateMode, normalizedCycleType);
        var (normalizedStartDate, normalizedEndDate) = NormalizeDateRange(
            normalizedCycleType,
            normalizedRecurringDateMode,
            startDate,
            endDate);
        var (normalizedTimeStart, normalizedTimeEnd) = NormalizeTimeRange(timeStart, timeEnd);

        var normalizedWeekDays = NormalizeWeekDays(normalizedCycleType, weekDays);
        var normalizedChannels = NormalizeChannels(channels);
        var normalizedPerUserLimit = NormalizeOptionalLimit(perUserLimit, "perUserLimit 参数不合法");
        var normalizedStoreIds = NormalizeStoreIds(storeIds);
        var normalizedProducts = NormalizeProducts(products, normalizedPerUserLimit);
        var normalizedMetrics = NormalizeMetrics(metrics ?? fallbackMetrics);

        return new FlashSaleRulesDto
        {
            CycleType = normalizedCycleType,
            RecurringDateMode = normalizedRecurringDateMode,
            StartDate = normalizedStartDate,
            EndDate = normalizedEndDate,
            TimeStart = normalizedTimeStart,
            TimeEnd = normalizedTimeEnd,
            WeekDays = normalizedWeekDays,
            Channels = normalizedChannels,
            PerUserLimit = normalizedPerUserLimit,
            StoreIds = normalizedStoreIds,
            Products = normalizedProducts,
            Metrics = normalizedMetrics
        };
    }

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

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

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

        if (string.Equals(rules.CycleType, CycleTypeRecurring, StringComparison.Ordinal) &&
            string.Equals(rules.RecurringDateMode, RecurringDateModeLongTerm, 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(FlashSaleRulesDto rules, DateTime nowUtc)
    {
        if (string.Equals(rules.CycleType, CycleTypeRecurring, StringComparison.Ordinal) &&
            string.Equals(rules.RecurringDateMode, RecurringDateModeLongTerm, StringComparison.Ordinal) &&
            (!rules.StartDate.HasValue || !rules.EndDate.HasValue))
        {
            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 NormalizeCycleType(string? value)
    {
        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        if (candidate is not (CycleTypeOnce or CycleTypeRecurring))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "cycleType 参数不合法");
        }

        return candidate;
    }

    private static string NormalizeRecurringDateMode(string? value, string cycleType)
    {
        if (string.Equals(cycleType, CycleTypeOnce, StringComparison.Ordinal))
        {
            return RecurringDateModeFixed;
        }

        var candidate = (value ?? string.Empty).Trim().ToLowerInvariant();
        if (candidate is not (RecurringDateModeFixed or RecurringDateModeLongTerm))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "recurringDateMode 参数不合法");
        }

        return candidate;
    }

    private static (DateTime? StartDate, DateTime? EndDate) NormalizeDateRange(
        string cycleType,
        string recurringDateMode,
        DateTime? startDate,
        DateTime? endDate)
    {
        var normalizedStart = startDate.HasValue ? NormalizeStartOfDay(startDate.Value) : (DateTime?)null;
        var normalizedEnd = endDate.HasValue ? NormalizeStartOfDay(endDate.Value) : (DateTime?)null;

        if (string.Equals(cycleType, CycleTypeOnce, StringComparison.Ordinal) ||
            string.Equals(recurringDateMode, RecurringDateModeFixed, StringComparison.Ordinal))
        {
            if (!normalizedStart.HasValue || !normalizedEnd.HasValue)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "startDate 与 endDate 不能为空");
            }

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

            return (normalizedStart, normalizedEnd);
        }

        if (normalizedStart.HasValue != normalizedEnd.HasValue)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "startDate 与 endDate 必须同时传入");
        }

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

        return (normalizedStart, normalizedEnd);
    }

    private static (TimeSpan? TimeStart, TimeSpan? TimeEnd) NormalizeTimeRange(TimeSpan? timeStart, TimeSpan? timeEnd)
    {
        if (!timeStart.HasValue && !timeEnd.HasValue)
        {
            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<int> NormalizeWeekDays(string cycleType, IReadOnlyCollection<int>? weekDays)
    {
        if (string.Equals(cycleType, CycleTypeOnce, StringComparison.Ordinal))
        {
            return [];
        }

        var normalized = (weekDays ?? Array.Empty<int>())
            .Where(day => day is >= 1 and <= 7)
            .Distinct()
            .OrderBy(day => day)
            .ToList();

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

        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 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<FlashSaleProductRuleDto> NormalizeProducts(
        IReadOnlyCollection<FlashSaleProductRuleDto>? products,
        int? campaignPerUserLimit)
    {
        var normalized = (products ?? Array.Empty<FlashSaleProductRuleDto>())
            .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 normalizedDiscountPrice = NormalizeMoney(item.DiscountPrice, "折扣价必须大于 0");
                if (normalizedDiscountPrice > normalizedOriginalPrice)
                {
                    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, "商品每人限购不能大于活动每人限购");
                }

                return new FlashSaleProductRuleDto
                {
                    ProductId = item.ProductId,
                    CategoryId = item.CategoryId,
                    CategoryName = (item.CategoryName ?? string.Empty).Trim(),
                    Name = normalizedName,
                    SpuCode = normalizedSpuCode,
                    Status = normalizedStatus,
                    OriginalPrice = normalizedOriginalPrice,
                    DiscountPrice = normalizedDiscountPrice,
                    PerUserLimit = normalizedPerUserLimit,
                    SoldCount = Math.Max(0, item.SoldCount)
                };
            })
            .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 FlashSaleMetricsDto NormalizeMetrics(FlashSaleMetricsDto? metrics)
    {
        if (metrics is null)
        {
            return new FlashSaleMetricsDto();
        }

        return new FlashSaleMetricsDto
        {
            ActivitySalesCount = Math.Max(0, metrics.ActivitySalesCount),
            DiscountTotalAmount = NormalizeNonNegativeMoney(metrics.DiscountTotalAmount),
            LoopedWeeks = Math.Max(0, metrics.LoopedWeeks),
            MonthlyDiscountSalesCount = Math.Max(0, metrics.MonthlyDiscountSalesCount)
        };
    }

    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);
    }
}
