using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Deliveries.Entities;
using TakeoutSaaS.Domain.Orders.Entities;
using TakeoutSaaS.Domain.Orders.Enums;
using TakeoutSaaS.Domain.Orders.Repositories;
using TakeoutSaaS.Domain.Payments.Entities;
using TakeoutSaaS.Domain.Payments.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// 订单聚合的 EF Core 仓储实现。
/// </summary>
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepository
{
    /// <inheritdoc />
    public Task<Order?> FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        return context.Orders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.Id == orderId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task<Order?> FindByOrderNoAsync(string orderNo, long tenantId, CancellationToken cancellationToken = default)
    {
        return context.Orders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.OrderNo == orderNo)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<Order>> SearchAsync(long tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default)
    {
        var query = context.Orders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId);

        if (status.HasValue)
        {
            query = query.Where(x => x.Status == status.Value);
        }

        if (paymentStatus.HasValue)
        {
            query = query.Where(x => x.PaymentStatus == paymentStatus.Value);
        }

        var orders = await query
            .OrderByDescending(x => x.CreatedAt)
            .ToListAsync(cancellationToken);

        return orders;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<Order>> SearchAllOrdersAsync(
        long tenantId,
        long? storeId,
        DateTime? startAt,
        DateTime? endAt,
        OrderStatus? status,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword,
        CancellationToken cancellationToken = default)
    {
        var query = BuildSearchAllOrdersQuery(
            tenantId,
            storeId,
            startAt,
            endAt,
            status,
            false,
            deliveryType,
            paymentMethod,
            keyword);

        return await query
            .OrderByDescending(x => x.CreatedAt)
            .ThenByDescending(x => x.Id)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<(IReadOnlyList<Order> Items, int TotalCount)> SearchAllOrdersPageAsync(
        long tenantId,
        long? storeId,
        DateTime? startAt,
        DateTime? endAt,
        OrderStatus? status,
        bool refundedOnly,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword,
        string? sortBy,
        bool sortDescending,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 200);

        var query = BuildSearchAllOrdersQuery(
            tenantId,
            storeId,
            startAt,
            endAt,
            status,
            refundedOnly,
            deliveryType,
            paymentMethod,
            keyword);

        var total = await query.CountAsync(cancellationToken);
        var sorted = ApplyOrderSorting(query, sortBy, sortDescending);

        var items = await sorted
            .Skip((normalizedPage - 1) * normalizedPageSize)
            .Take(normalizedPageSize)
            .ToListAsync(cancellationToken);

        return (items, total);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        var items = await context.OrderItems
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .OrderBy(x => x.Id)
            .ToListAsync(cancellationToken);

        return items;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyDictionary<long, IReadOnlyList<OrderItem>>> GetItemsByOrderIdsAsync(
        IReadOnlyCollection<long> orderIds,
        long tenantId,
        CancellationToken cancellationToken = default)
    {
        if (orderIds.Count == 0)
        {
            return new Dictionary<long, IReadOnlyList<OrderItem>>();
        }

        var items = await context.OrderItems
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && orderIds.Contains(x.OrderId))
            .OrderBy(x => x.OrderId)
            .ThenBy(x => x.Id)
            .ToListAsync(cancellationToken);

        return items
            .GroupBy(item => item.OrderId)
            .ToDictionary(group => group.Key, group => (IReadOnlyList<OrderItem>)group.ToList());
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<OrderStatusHistory>> GetStatusHistoryAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        var histories = await context.OrderStatusHistories
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .OrderBy(x => x.CreatedAt)
            .ToListAsync(cancellationToken);

        return histories;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<RefundRequest>> GetRefundsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        var refunds = await context.RefundRequests
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .OrderByDescending(x => x.CreatedAt)
            .ToListAsync(cancellationToken);

        return refunds;
    }

    /// <inheritdoc />
    public async Task<IReadOnlySet<long>> GetRefundedOrderIdsAsync(
        IReadOnlyCollection<long> orderIds,
        long tenantId,
        CancellationToken cancellationToken = default)
    {
        if (orderIds.Count == 0)
        {
            return new HashSet<long>();
        }

        var result = await context.RefundRequests
            .AsNoTracking()
            .Where(x =>
                x.TenantId == tenantId &&
                orderIds.Contains(x.OrderId) &&
                x.Status == RefundStatus.Refunded)
            .Select(x => x.OrderId)
            .Distinct()
            .ToListAsync(cancellationToken);

        return result.ToHashSet();
    }

    /// <inheritdoc />
    public Task<PaymentRecord?> GetLatestPaymentRecordAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        return context.PaymentRecords
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .OrderByDescending(x => x.PaidAt ?? DateTime.MinValue)
            .ThenByDescending(x => x.CreatedAt)
            .ThenByDescending(x => x.Id)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task<DeliveryOrder?> GetLatestDeliveryOrderAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        return context.DeliveryOrders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .OrderByDescending(x => x.DeliveredAt ?? DateTime.MinValue)
            .ThenByDescending(x => x.PickedUpAt ?? DateTime.MinValue)
            .ThenByDescending(x => x.CreatedAt)
            .ThenByDescending(x => x.Id)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task AddOrderAsync(Order order, CancellationToken cancellationToken = default)
    {
        return context.Orders.AddAsync(order, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task AddItemsAsync(IEnumerable<OrderItem> items, CancellationToken cancellationToken = default)
    {
        return context.OrderItems.AddRangeAsync(items, cancellationToken);
    }

    /// <inheritdoc />
    public Task AddStatusHistoryAsync(OrderStatusHistory history, CancellationToken cancellationToken = default)
    {
        return context.OrderStatusHistories.AddAsync(history, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task AddRefundAsync(RefundRequest refund, CancellationToken cancellationToken = default)
    {
        return context.RefundRequests.AddAsync(refund, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return context.SaveChangesAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task UpdateOrderAsync(Order order, CancellationToken cancellationToken = default)
    {
        context.Orders.Update(order);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task DeleteOrderAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
    {
        var items = await context.OrderItems
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .ToListAsync(cancellationToken);
        if (items.Count > 0)
        {
            context.OrderItems.RemoveRange(items);
        }

        var histories = await context.OrderStatusHistories
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .ToListAsync(cancellationToken);
        if (histories.Count > 0)
        {
            context.OrderStatusHistories.RemoveRange(histories);
        }

        var refunds = await context.RefundRequests
            .Where(x => x.TenantId == tenantId && x.OrderId == orderId)
            .ToListAsync(cancellationToken);
        if (refunds.Count > 0)
        {
            context.RefundRequests.RemoveRange(refunds);
        }

        var existing = await context.Orders
            .Where(x => x.TenantId == tenantId && x.Id == orderId)
            .FirstOrDefaultAsync(cancellationToken);
        if (existing == null)
        {
            return;
        }

        context.Orders.Remove(existing);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<Order>> GetActiveOrdersAsync(long tenantId, long storeId, CancellationToken cancellationToken = default)
    {
        // 查询待接单、制作中、待取餐的活跃订单
        var activeStatuses = new[]
        {
            OrderStatus.AwaitingPreparation,
            OrderStatus.InProgress,
            OrderStatus.Ready
        };

        return await context.Orders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == storeId && activeStatuses.Contains(x.Status))
            .OrderBy(x => x.CreatedAt)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<Order>> GetOrdersChangedSinceAsync(long tenantId, long storeId, DateTime since, CancellationToken cancellationToken = default)
    {
        // 查询指定时间后创建或更新的订单
        return await context.Orders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == storeId && x.UpdatedAt >= since)
            .OrderByDescending(x => x.UpdatedAt)
            .ToListAsync(cancellationToken);
    }

    private IQueryable<Order> BuildSearchAllOrdersQuery(
        long tenantId,
        long? storeId,
        DateTime? startAt,
        DateTime? endAt,
        OrderStatus? status,
        bool refundedOnly,
        DeliveryType? deliveryType,
        PaymentMethod? paymentMethod,
        string? keyword)
    {
        var query = context.Orders
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId);

        if (storeId.HasValue)
        {
            query = query.Where(x => x.StoreId == storeId.Value);
        }

        if (startAt.HasValue)
        {
            query = query.Where(x => x.CreatedAt >= startAt.Value);
        }

        if (endAt.HasValue)
        {
            query = query.Where(x => x.CreatedAt < endAt.Value);
        }

        if (status.HasValue)
        {
            query = query.Where(x => x.Status == status.Value);
        }

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

        if (paymentMethod.HasValue)
        {
            query = query.Where(x =>
                context.PaymentRecords.Any(record =>
                    record.TenantId == tenantId &&
                    record.OrderId == x.Id &&
                    record.Method == paymentMethod.Value &&
                    (record.Status == PaymentStatus.Paid || record.Status == PaymentStatus.Refunded)));
        }

        if (refundedOnly)
        {
            query = query.Where(x =>
                context.RefundRequests.Any(refund =>
                    refund.TenantId == tenantId &&
                    refund.OrderId == x.Id &&
                    refund.Status == RefundStatus.Refunded));
        }

        if (!string.IsNullOrWhiteSpace(keyword))
        {
            var normalized = keyword.Trim();
            query = query.Where(x =>
                x.OrderNo.Contains(normalized) ||
                (x.CustomerPhone != null && x.CustomerPhone.Contains(normalized)));
        }

        return query;
    }

    private static IOrderedQueryable<Order> ApplyOrderSorting(
        IQueryable<Order> query,
        string? sortBy,
        bool sortDescending)
    {
        return sortBy?.Trim().ToLowerInvariant() switch
        {
            "amount" => sortDescending
                ? query
                    .OrderByDescending(order => order.PaidAmount > 0 ? order.PaidAmount : order.PayableAmount)
                    .ThenByDescending(order => order.CreatedAt)
                    .ThenByDescending(order => order.Id)
                : query
                    .OrderBy(order => order.PaidAmount > 0 ? order.PaidAmount : order.PayableAmount)
                    .ThenBy(order => order.CreatedAt)
                    .ThenBy(order => order.Id),
            "status" => sortDescending
                ? query
                    .OrderByDescending(order => order.Status)
                    .ThenByDescending(order => order.CreatedAt)
                    .ThenByDescending(order => order.Id)
                : query
                    .OrderBy(order => order.Status)
                    .ThenBy(order => order.CreatedAt)
                    .ThenBy(order => order.Id),
            _ => sortDescending
                ? query.OrderByDescending(order => order.CreatedAt).ThenByDescending(order => order.Id)
                : query.OrderBy(order => order.CreatedAt).ThenBy(order => order.Id)
        };
    }
}
