using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Domain.Finance.Models;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Domain.Membership.Enums;
using TakeoutSaaS.Domain.Orders.Enums;
using TakeoutSaaS.Domain.Payments.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// 财务交易流水 EF Core 仓储实现。
/// </summary>
public sealed class EfFinanceTransactionRepository(TakeoutAppDbContext context) : IFinanceTransactionRepository
{
    /// <inheritdoc />
    public async Task<FinanceTransactionPageSnapshot> SearchPageAsync(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        FinanceTransactionType? transactionType,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        // 1. 构建筛选查询并读取总量。
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 200);
        var query = BuildQuery(tenantId, storeId, startAt, endAt, transactionType, deliveryType, paymentMethod, keyword);
        var totalCount = await query.CountAsync(cancellationToken);

        if (totalCount == 0)
        {
            return new FinanceTransactionPageSnapshot
            {
                Items = [],
                TotalCount = 0,
                PageIncomeAmount = 0,
                PageRefundAmount = 0
            };
        }

        // 2. 分页读取记录并完成统一映射。
        var rows = await query
            .OrderByDescending(item => item.OccurredAt)
            .ThenByDescending(item => item.SourceId)
            .Skip((normalizedPage - 1) * normalizedPageSize)
            .Take(normalizedPageSize)
            .ToListAsync(cancellationToken);

        var items = rows.Select(MapToRecord).ToList();

        // 3. 汇总本页收入与退款。
        var pageIncomeAmount = items
            .Where(item => item.AmountSigned > 0)
            .Sum(item => item.AmountSigned);
        var pageRefundAmount = items
            .Where(item => item.AmountSigned < 0)
            .Sum(item => Math.Abs(item.AmountSigned));

        return new FinanceTransactionPageSnapshot
        {
            Items = items,
            TotalCount = totalCount,
            PageIncomeAmount = decimal.Round(pageIncomeAmount, 2, MidpointRounding.AwayFromZero),
            PageRefundAmount = decimal.Round(pageRefundAmount, 2, MidpointRounding.AwayFromZero)
        };
    }

    /// <inheritdoc />
    public async Task<FinanceTransactionStatsSnapshot> GetStatsAsync(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        FinanceTransactionType? transactionType,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword,
        CancellationToken cancellationToken = default)
    {
        // 1. 构建筛选查询并聚合总览指标。
        var query = BuildQuery(tenantId, storeId, startAt, endAt, transactionType, deliveryType, paymentMethod, keyword);

        var summary = await query
            .GroupBy(_ => 1)
            .Select(group => new
            {
                TotalIncome = group
                    .Where(item => item.AmountSigned > 0)
                    .Sum(item => item.AmountSigned),
                TotalRefund = group
                    .Where(item => item.AmountSigned < 0)
                    .Sum(item => Math.Abs(item.AmountSigned)),
                TotalCount = group.Count()
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (summary is null)
        {
            return new FinanceTransactionStatsSnapshot
            {
                TotalIncome = 0,
                TotalRefund = 0,
                TotalCount = 0
            };
        }

        return new FinanceTransactionStatsSnapshot
        {
            TotalIncome = decimal.Round(summary.TotalIncome, 2, MidpointRounding.AwayFromZero),
            TotalRefund = decimal.Round(summary.TotalRefund, 2, MidpointRounding.AwayFromZero),
            TotalCount = summary.TotalCount
        };
    }

    /// <inheritdoc />
    public async Task<FinanceTransactionRecord?> GetDetailAsync(
        long tenantId,
        long storeId,
        FinanceTransactionSourceType sourceType,
        long sourceId,
        CancellationToken cancellationToken = default)
    {
        // 1. 按来源组合键定位唯一流水。
        var query = BuildQuery(
            tenantId,
            storeId,
            startAt: null,
            endAt: null,
            transactionType: null,
            deliveryType: null,
            paymentMethod: null,
            keyword: null);

        var row = await query
            .Where(item => item.SourceType == sourceType && item.SourceId == sourceId)
            .OrderByDescending(item => item.OccurredAt)
            .ThenByDescending(item => item.SourceId)
            .FirstOrDefaultAsync(cancellationToken);

        return row is null ? null : MapToRecord(row);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<FinanceTransactionRecord>> ListForExportAsync(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        FinanceTransactionType? transactionType,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword,
        CancellationToken cancellationToken = default)
    {
        // 1. 按筛选读取导出数据，限定上限保障性能。
        var query = BuildQuery(tenantId, storeId, startAt, endAt, transactionType, deliveryType, paymentMethod, keyword);

        var rows = await query
            .OrderByDescending(item => item.OccurredAt)
            .ThenByDescending(item => item.SourceId)
            .Take(20_000)
            .ToListAsync(cancellationToken);

        return rows.Select(MapToRecord).ToList();
    }

    /// <inheritdoc />
    public async Task<FinanceSettlementStatsSnapshot> GetSettlementStatsAsync(
        long tenantId,
        long storeId,
        DateTime currentUtc,
        CancellationToken cancellationToken = default)
    {
        var utcNow = NormalizeUtc(currentUtc);
        var todayStart = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, 0, 0, 0, DateTimeKind.Utc);
        var tomorrowStart = todayStart.AddDays(1);
        var yesterdayStart = todayStart.AddDays(-1);
        var monthStart = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc);
        var monthEnd = monthStart.AddMonths(1);

        var query = BuildSettlementPaymentQuery(
            tenantId,
            storeId,
            startAt: null,
            endAt: null,
            paymentMethod: null);

        var summary = await query
            .GroupBy(_ => 1)
            .Select(group => new
            {
                TodayArrivedAmount = group
                    .Where(item => item.PaidAt >= todayStart && item.PaidAt < tomorrowStart)
                    .Sum(item => item.Amount),
                YesterdayArrivedAmount = group
                    .Where(item => item.PaidAt >= yesterdayStart && item.PaidAt < todayStart)
                    .Sum(item => item.Amount),
                CurrentMonthArrivedAmount = group
                    .Where(item => item.PaidAt >= monthStart && item.PaidAt < monthEnd)
                    .Sum(item => item.Amount),
                CurrentMonthTransactionCount = group
                    .Count(item => item.PaidAt >= monthStart && item.PaidAt < monthEnd)
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (summary is null)
        {
            return new FinanceSettlementStatsSnapshot
            {
                TodayArrivedAmount = 0,
                YesterdayArrivedAmount = 0,
                CurrentMonthArrivedAmount = 0,
                CurrentMonthTransactionCount = 0
            };
        }

        return new FinanceSettlementStatsSnapshot
        {
            TodayArrivedAmount = decimal.Round(summary.TodayArrivedAmount, 2, MidpointRounding.AwayFromZero),
            YesterdayArrivedAmount = decimal.Round(summary.YesterdayArrivedAmount, 2, MidpointRounding.AwayFromZero),
            CurrentMonthArrivedAmount = decimal.Round(summary.CurrentMonthArrivedAmount, 2, MidpointRounding.AwayFromZero),
            CurrentMonthTransactionCount = summary.CurrentMonthTransactionCount
        };
    }

    /// <inheritdoc />
    public async Task<FinanceSettlementAccountSnapshot?> GetSettlementAccountAsync(
        long tenantId,
        CancellationToken cancellationToken = default)
    {
        var profile = await context.TenantVerificationProfiles
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.DeletedAt == null)
            .Select(item => new
            {
                item.BankName,
                item.BankAccountName,
                item.BankAccountNumber,
                item.WeChatMerchantNo,
                item.AlipayPid
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (profile is null)
        {
            return null;
        }

        return new FinanceSettlementAccountSnapshot
        {
            BankName = (profile.BankName ?? string.Empty).Trim(),
            BankAccountName = (profile.BankAccountName ?? string.Empty).Trim(),
            BankAccountNoMasked = MaskBankAccountNo(profile.BankAccountNumber),
            WechatMerchantNoMasked = MaskWechatMerchantNo(profile.WeChatMerchantNo),
            AlipayPidMasked = MaskAlipayPid(profile.AlipayPid),
            SettlementPeriodText = "T+1 自动到账"
        };
    }

    /// <inheritdoc />
    public async Task<FinanceSettlementPageSnapshot> SearchSettlementPageAsync(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        PaymentMethod? paymentMethod,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 200);

        var groupedQuery = BuildSettlementPaymentQuery(tenantId, storeId, startAt, endAt, paymentMethod)
            .GroupBy(item => new { ArrivedDate = item.PaidAt.Date, item.PaymentMethod })
            .Select(group => new FinanceSettlementListItemSnapshot
            {
                ArrivedDate = DateTime.SpecifyKind(group.Key.ArrivedDate, DateTimeKind.Utc),
                PaymentMethod = group.Key.PaymentMethod,
                TransactionCount = group.Count(),
                ArrivedAmount = decimal.Round(group.Sum(item => item.Amount), 2, MidpointRounding.AwayFromZero)
            });

        var totalCount = await groupedQuery.CountAsync(cancellationToken);
        if (totalCount == 0)
        {
            return new FinanceSettlementPageSnapshot
            {
                Items = [],
                TotalCount = 0
            };
        }

        var items = await groupedQuery
            .OrderByDescending(item => item.ArrivedDate)
            .ThenBy(item => item.PaymentMethod)
            .Skip((normalizedPage - 1) * normalizedPageSize)
            .Take(normalizedPageSize)
            .ToListAsync(cancellationToken);

        return new FinanceSettlementPageSnapshot
        {
            Items = items,
            TotalCount = totalCount
        };
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<FinanceSettlementDetailItemSnapshot>> GetSettlementDetailsAsync(
        long tenantId,
        long storeId,
        DateTime arrivedDate,
        PaymentMethod paymentMethod,
        int take,
        CancellationToken cancellationToken = default)
    {
        var arrivedDay = NormalizeUtc(arrivedDate);
        var dayStart = new DateTime(arrivedDay.Year, arrivedDay.Month, arrivedDay.Day, 0, 0, 0, DateTimeKind.Utc);
        var dayEnd = dayStart.AddDays(1);
        var normalizedTake = Math.Clamp(take, 1, 200);

        return await BuildSettlementPaymentQuery(
                tenantId,
                storeId,
                dayStart,
                dayEnd,
                paymentMethod)
            .OrderByDescending(item => item.PaidAt)
            .ThenByDescending(item => item.PaymentRecordId)
            .Select(item => new FinanceSettlementDetailItemSnapshot
            {
                OrderNo = item.OrderNo,
                Amount = decimal.Round(item.Amount, 2, MidpointRounding.AwayFromZero),
                PaidAt = item.PaidAt
            })
            .Take(normalizedTake)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<FinanceSettlementListItemSnapshot>> ListSettlementForExportAsync(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        PaymentMethod? paymentMethod,
        CancellationToken cancellationToken = default)
    {
        return await BuildSettlementPaymentQuery(tenantId, storeId, startAt, endAt, paymentMethod)
            .GroupBy(item => new { ArrivedDate = item.PaidAt.Date, item.PaymentMethod })
            .Select(group => new FinanceSettlementListItemSnapshot
            {
                ArrivedDate = DateTime.SpecifyKind(group.Key.ArrivedDate, DateTimeKind.Utc),
                PaymentMethod = group.Key.PaymentMethod,
                TransactionCount = group.Count(),
                ArrivedAmount = decimal.Round(group.Sum(item => item.Amount), 2, MidpointRounding.AwayFromZero)
            })
            .OrderByDescending(item => item.ArrivedDate)
            .ThenBy(item => item.PaymentMethod)
            .Take(20_000)
            .ToListAsync(cancellationToken);
    }

    private IQueryable<TransactionProjection> BuildQuery(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        FinanceTransactionType? transactionType,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword)
    {
        // 1. 收入流水（支付成功）。
        var incomeQuery =
            from payment in context.PaymentRecords.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on payment.OrderId equals order.Id
            where payment.TenantId == tenantId
                  && order.TenantId == tenantId
                  && payment.Status == PaymentStatus.Paid
            select new TransactionProjection
            {
                SourceType = FinanceTransactionSourceType.PaymentRecord,
                SourceId = payment.Id,
                StoreId = order.StoreId,
                TransactionType = FinanceTransactionType.Income,
                TransactionNo = payment.TradeNo,
                OrderId = order.Id,
                OrderNo = order.OrderNo,
                DeliveryType = order.DeliveryType,
                PaymentMethod = payment.Method,
                AmountSigned = payment.Amount,
                OccurredAt = payment.PaidAt ?? payment.CreatedAt,
                Remark = payment.Remark,
                CustomerName = order.CustomerName,
                CustomerPhone = order.CustomerPhone
            };

        // 2. 渠道退款流水（优先来源）。
        var channelRefundQuery =
            from refund in context.PaymentRefundRecords.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on refund.OrderId equals order.Id
            join payment in context.PaymentRecords.AsNoTracking()
                on refund.PaymentRecordId equals payment.Id into paymentGroup
            from matchedPayment in paymentGroup.DefaultIfEmpty()
            where refund.TenantId == tenantId
                  && order.TenantId == tenantId
                  && refund.Status == PaymentRefundStatus.Succeeded
            select new TransactionProjection
            {
                SourceType = FinanceTransactionSourceType.PaymentRefundRecord,
                SourceId = refund.Id,
                StoreId = order.StoreId,
                TransactionType = FinanceTransactionType.Refund,
                TransactionNo = refund.ChannelRefundId,
                OrderId = order.Id,
                OrderNo = order.OrderNo,
                DeliveryType = order.DeliveryType,
                PaymentMethod = matchedPayment == null ? null : matchedPayment.Method,
                AmountSigned = 0 - refund.Amount,
                OccurredAt = refund.CompletedAt ?? refund.RequestedAt,
                Remark = null,
                CustomerName = order.CustomerName,
                CustomerPhone = order.CustomerPhone
            };

        // 3. 退款申请流水（补充来源：没有渠道退款记录时使用）。
        var requestRefundQuery =
            from refund in context.RefundRequests.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on refund.OrderId equals order.Id
            where refund.TenantId == tenantId
                  && order.TenantId == tenantId
                  && refund.Status == RefundStatus.Refunded
                  && !context.PaymentRefundRecords
                      .AsNoTracking()
                      .Any(channelRefund =>
                          channelRefund.TenantId == tenantId
                          && channelRefund.OrderId == refund.OrderId
                          && channelRefund.Status == PaymentRefundStatus.Succeeded)
            select new TransactionProjection
            {
                SourceType = FinanceTransactionSourceType.RefundRequest,
                SourceId = refund.Id,
                StoreId = order.StoreId,
                TransactionType = FinanceTransactionType.Refund,
                TransactionNo = refund.RefundNo,
                OrderId = order.Id,
                OrderNo = order.OrderNo,
                DeliveryType = order.DeliveryType,
                PaymentMethod = context.PaymentRecords
                    .AsNoTracking()
                    .Where(payment => payment.TenantId == tenantId && payment.OrderId == order.Id)
                    .OrderByDescending(payment => payment.PaidAt ?? payment.CreatedAt)
                    .ThenByDescending(payment => payment.Id)
                    .Select(payment => (PaymentMethod?)payment.Method)
                    .FirstOrDefault(),
                AmountSigned = 0 - refund.Amount,
                OccurredAt = refund.ProcessedAt ?? refund.RequestedAt,
                Remark = refund.ReviewNotes,
                RefundNo = refund.RefundNo,
                RefundReason = refund.Reason,
                CustomerName = order.CustomerName,
                CustomerPhone = order.CustomerPhone
            };

        // 4. 储值充值流水。
        var rechargeQuery =
            from recharge in context.MemberStoredCardRechargeRecords.AsNoTracking()
            where recharge.TenantId == tenantId
            select new TransactionProjection
            {
                SourceType = FinanceTransactionSourceType.StoredCardRechargeRecord,
                SourceId = recharge.Id,
                StoreId = recharge.StoreId,
                TransactionType = FinanceTransactionType.StoredCardRecharge,
                TransactionNo = recharge.RecordNo,
                OrderId = null,
                OrderNo = null,
                DeliveryType = null,
                PaymentMethod = recharge.PaymentMethod,
                AmountSigned = recharge.RechargeAmount,
                OccurredAt = recharge.RechargedAt,
                Remark = recharge.Remark,
                MemberName = recharge.MemberName,
                MemberMobileMasked = recharge.MemberMobileMasked,
                RechargeAmount = recharge.RechargeAmount,
                GiftAmount = recharge.GiftAmount,
                ArrivedAmount = recharge.ArrivedAmount
            };

        // 5. 积分抵扣流水（仅统计可关联订单）。
        var pointRedeemQuery =
            from pointLedger in context.MemberPointLedgers.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on pointLedger.SourceId equals (long?)order.Id
            where pointLedger.TenantId == tenantId
                  && order.TenantId == tenantId
                  && pointLedger.SourceId.HasValue
                  && pointLedger.Reason == PointChangeReason.Redeem
                  && pointLedger.ChangeAmount < 0
            select new TransactionProjection
            {
                SourceType = FinanceTransactionSourceType.MemberPointLedger,
                SourceId = pointLedger.Id,
                StoreId = order.StoreId,
                TransactionType = FinanceTransactionType.PointRedeem,
                TransactionNo = null,
                OrderId = order.Id,
                OrderNo = order.OrderNo,
                DeliveryType = order.DeliveryType,
                PaymentMethod = context.PaymentRecords
                    .AsNoTracking()
                    .Where(payment => payment.TenantId == tenantId && payment.OrderId == order.Id)
                    .OrderByDescending(payment => payment.PaidAt ?? payment.CreatedAt)
                    .ThenByDescending(payment => payment.Id)
                    .Select(payment => (PaymentMethod?)payment.Method)
                    .FirstOrDefault(),
                AmountSigned = order.DiscountAmount,
                OccurredAt = pointLedger.OccurredAt,
                Remark = null,
                PointChangeAmount = pointLedger.ChangeAmount,
                PointBalanceAfterChange = pointLedger.BalanceAfterChange,
                CustomerName = order.CustomerName,
                CustomerPhone = order.CustomerPhone
            };

        // 6. 合并多来源并下推统一筛选条件。
        var query = incomeQuery
            .Concat(channelRefundQuery)
            .Concat(requestRefundQuery)
            .Concat(rechargeQuery)
            .Concat(pointRedeemQuery)
            .Where(item => item.StoreId == storeId);

        if (startAt.HasValue)
        {
            query = query.Where(item => item.OccurredAt >= startAt.Value);
        }

        if (endAt.HasValue)
        {
            query = query.Where(item => item.OccurredAt < endAt.Value);
        }

        if (transactionType.HasValue)
        {
            query = query.Where(item => item.TransactionType == transactionType.Value);
        }

        if (deliveryType.HasValue)
        {
            query = query.Where(item => item.DeliveryType == deliveryType.Value);
        }

        if (paymentMethod.HasValue)
        {
            query = query.Where(item => item.PaymentMethod == paymentMethod.Value);
        }

        var normalizedKeyword = (keyword ?? string.Empty).Trim();
        if (!string.IsNullOrWhiteSpace(normalizedKeyword))
        {
            var like = $"%{normalizedKeyword}%";
            query = query.Where(item =>
                EF.Functions.ILike(item.TransactionNo ?? string.Empty, like)
                || EF.Functions.ILike(item.OrderNo ?? string.Empty, like)
                || EF.Functions.ILike(item.RefundNo ?? string.Empty, like)
                || EF.Functions.ILike(item.MemberName ?? string.Empty, like)
                || EF.Functions.ILike(item.MemberMobileMasked ?? string.Empty, like));
        }

        return query;
    }

    private IQueryable<SettlementPaymentProjection> BuildSettlementPaymentQuery(
        long tenantId,
        long storeId,
        DateTime? startAt,
        DateTime? endAt,
        PaymentMethod? paymentMethod)
    {
        var query =
            from payment in context.PaymentRecords.AsNoTracking()
            join order in context.Orders.AsNoTracking()
                on payment.OrderId equals order.Id
            where payment.TenantId == tenantId
                  && order.TenantId == tenantId
                  && order.StoreId == storeId
                  && payment.Status == PaymentStatus.Paid
                  && payment.PaidAt.HasValue
                  && (payment.Method == PaymentMethod.WeChatPay || payment.Method == PaymentMethod.Alipay)
            select new SettlementPaymentProjection
            {
                PaymentRecordId = payment.Id,
                OrderNo = order.OrderNo,
                PaymentMethod = payment.Method,
                Amount = payment.Amount,
                PaidAt = payment.PaidAt!.Value
            };

        if (startAt.HasValue)
        {
            query = query.Where(item => item.PaidAt >= startAt.Value);
        }

        if (endAt.HasValue)
        {
            query = query.Where(item => item.PaidAt < endAt.Value);
        }

        if (paymentMethod.HasValue)
        {
            query = query.Where(item => item.PaymentMethod == paymentMethod.Value);
        }

        return query;
    }

    private static FinanceTransactionRecord MapToRecord(TransactionProjection source)
    {
        return new FinanceTransactionRecord
        {
            SourceType = source.SourceType,
            SourceId = source.SourceId,
            StoreId = source.StoreId,
            TransactionNo = ResolveTransactionNo(source),
            TransactionType = source.TransactionType,
            OrderId = source.OrderId,
            OrderNo = source.OrderNo,
            DeliveryType = source.DeliveryType,
            PaymentMethod = source.PaymentMethod,
            AmountSigned = decimal.Round(source.AmountSigned, 2, MidpointRounding.AwayFromZero),
            OccurredAt = source.OccurredAt,
            Remark = ResolveRemark(source),
            CustomerName = source.CustomerName,
            CustomerPhone = source.CustomerPhone,
            RefundNo = source.RefundNo,
            RefundReason = source.RefundReason,
            MemberName = source.MemberName,
            MemberMobileMasked = source.MemberMobileMasked,
            RechargeAmount = source.RechargeAmount,
            GiftAmount = source.GiftAmount,
            ArrivedAmount = source.ArrivedAmount,
            PointChangeAmount = source.PointChangeAmount,
            PointBalanceAfterChange = source.PointBalanceAfterChange
        };
    }

    private static string ResolveTransactionNo(TransactionProjection source)
    {
        if (!string.IsNullOrWhiteSpace(source.TransactionNo))
        {
            return source.TransactionNo.Trim();
        }

        return source.SourceType switch
        {
            FinanceTransactionSourceType.PaymentRecord => $"PAY{source.SourceId}",
            FinanceTransactionSourceType.PaymentRefundRecord => $"REFUND{source.SourceId}",
            FinanceTransactionSourceType.RefundRequest => $"REFREQ{source.SourceId}",
            FinanceTransactionSourceType.StoredCardRechargeRecord => $"RECHARGE{source.SourceId}",
            FinanceTransactionSourceType.MemberPointLedger => $"POINT{source.SourceId}",
            _ => $"TXN{source.SourceId}"
        };
    }

    private static string ResolveRemark(TransactionProjection source)
    {
        if (!string.IsNullOrWhiteSpace(source.Remark))
        {
            return source.Remark.Trim();
        }

        if (source.TransactionType == FinanceTransactionType.PointRedeem && source.PointChangeAmount.HasValue)
        {
            return $"积分抵扣{Math.Abs(source.PointChangeAmount.Value)}分";
        }

        return source.TransactionType switch
        {
            FinanceTransactionType.Income => "订单收款",
            FinanceTransactionType.Refund => "订单退款",
            FinanceTransactionType.StoredCardRecharge => "会员储值充值",
            FinanceTransactionType.PointRedeem => "积分抵扣",
            _ => "--"
        };
    }

    private sealed class TransactionProjection
    {
        public required FinanceTransactionSourceType SourceType { get; init; }

        public required long SourceId { get; init; }

        public required long StoreId { get; init; }

        public string? TransactionNo { get; init; }

        public required FinanceTransactionType TransactionType { get; init; }

        public long? OrderId { get; init; }

        public string? OrderNo { get; init; }

        public DeliveryType? DeliveryType { get; init; }

        public PaymentMethod? PaymentMethod { get; init; }

        public required decimal AmountSigned { get; init; }

        public required DateTime OccurredAt { get; init; }

        public string? Remark { get; init; }

        public string? CustomerName { get; init; }

        public string? CustomerPhone { get; init; }

        public string? RefundNo { get; init; }

        public string? RefundReason { get; init; }

        public string? MemberName { get; init; }

        public string? MemberMobileMasked { get; init; }

        public decimal? RechargeAmount { get; init; }

        public decimal? GiftAmount { get; init; }

        public decimal? ArrivedAmount { get; init; }

        public int? PointChangeAmount { get; init; }

        public int? PointBalanceAfterChange { get; init; }
    }

    private sealed class SettlementPaymentProjection
    {
        public required long PaymentRecordId { get; init; }

        public required string OrderNo { get; init; }

        public required PaymentMethod PaymentMethod { get; init; }

        public required decimal Amount { get; init; }

        public required DateTime PaidAt { get; init; }
    }

    private static DateTime NormalizeUtc(DateTime value)
    {
        return value.Kind switch
        {
            DateTimeKind.Utc => value,
            DateTimeKind.Local => value.ToUniversalTime(),
            _ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
        };
    }

    private static string MaskBankAccountNo(string? value)
    {
        var digits = new string((value ?? string.Empty).Where(char.IsDigit).ToArray());
        if (digits.Length >= 4)
        {
            return $"****{digits[^4..]}";
        }

        return digits;
    }

    private static string MaskWechatMerchantNo(string? value)
    {
        var normalized = (value ?? string.Empty).Trim();
        if (normalized.Length >= 4)
        {
            return $"{normalized[..2]}{new string('x', normalized.Length - 2)}";
        }

        return normalized;
    }

    private static string MaskAlipayPid(string? value)
    {
        var normalized = (value ?? string.Empty).Trim();
        if (normalized.Length > 6)
        {
            return $"{normalized[..4]}{new string('x', normalized.Length - 4)}";
        }

        return normalized;
    }
}
