// EN: Query to get all merchants with pagination for admin.
// VI: Query để lấy tất cả merchants với phân trang cho admin.

using MediatR;
using Microsoft.EntityFrameworkCore;
using MerchantService.Domain.AggregatesModel.MerchantAggregate;
using MerchantService.Infrastructure;

namespace MerchantService.API.Application.Queries.Admin;

/// <summary>
/// EN: Query to get all merchants with filters (Admin only).
/// VI: Query để lấy tất cả merchants với bộ lọc (chỉ Admin).
/// </summary>
public record GetAllMerchantsQuery(
    int Page = 1,
    int PageSize = 20,
    string? Status = null,
    string? VerificationStatus = null,
    string? Search = null) : IRequest<AdminMerchantListResultDto>;

/// <summary>
/// EN: Handler — uses EF.Property for private backing fields (Merchant uses DDD pattern).
/// VI: Handler — dùng EF.Property cho private backing fields (Merchant dùng DDD pattern).
/// </summary>
public class GetAllMerchantsQueryHandler : IRequestHandler<GetAllMerchantsQuery, AdminMerchantListResultDto>
{
    private readonly MerchantServiceContext _context;

    public GetAllMerchantsQueryHandler(MerchantServiceContext context)
    {
        _context = context;
    }

    public async Task<AdminMerchantListResultDto> Handle(
        GetAllMerchantsQuery request,
        CancellationToken cancellationToken)
    {
        // EN: All Merchant public properties are Ignored in EF config (DDD pattern with private fields).
        //     Must use EF.Property<T>(entity, "_fieldName") or join on Id columns.
        // VI: Tất cả public properties của Merchant bị Ignore trong EF config (DDD pattern với private fields).
        //     Phải dùng EF.Property<T>(entity, "_fieldName") hoặc join qua Id columns.
        var query = _context.Merchants
            .AsNoTracking()
            .Where(m => !EF.Property<bool>(m, "_isDeleted"));

        // EN: Filter by status — join with enumeration table to match by name
        // VI: Filter theo status — join với bảng enumeration để match theo tên
        if (!string.IsNullOrEmpty(request.Status))
        {
            var statusIds = _context.Set<MerchantStatus>()
                .Where(s => s.Name == request.Status).Select(s => s.Id);
            query = query.Where(m => statusIds.Contains(m.StatusId));
        }

        if (!string.IsNullOrEmpty(request.VerificationStatus))
        {
            var verifIds = _context.Set<VerificationStatus>()
                .Where(v => v.Name == request.VerificationStatus).Select(v => v.Id);
            query = query.Where(m => verifIds.Contains(m.VerificationStatusId));
        }

        if (!string.IsNullOrEmpty(request.Search))
        {
            var searchLower = request.Search.ToLower();
            query = query.Where(m => EF.Property<string>(m, "_businessName").ToLower().Contains(searchLower));
        }

        var totalCount = await query.CountAsync(cancellationToken);
        var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize);

        // EN: Step 1 — fetch merchant rows joined with enum tables (no subqueries)
        // VI: Bước 1 — lấy merchant rows join với bảng enum (không subquery)
        var rawItems = await query
            .OrderByDescending(m => EF.Property<DateTime>(m, "_createdAt"))
            .Skip((request.Page - 1) * request.PageSize)
            .Take(request.PageSize)
            .Join(_context.Set<MerchantType>(), m => m.TypeId, t => t.Id, (m, t) => new { m, TypeName = t.Name })
            .Join(_context.Set<MerchantStatus>(), x => x.m.StatusId, s => s.Id, (x, s) => new { x.m, x.TypeName, StatusName = s.Name })
            .Join(_context.Set<VerificationStatus>(), x => x.m.VerificationStatusId, v => v.Id, (x, v) => new { x.m, x.TypeName, x.StatusName, VerifName = v.Name })
            .Select(x => new
            {
                x.m.Id,
                UserId = EF.Property<Guid>(x.m, "_userId"),
                BusinessName = EF.Property<string>(x.m, "_businessName"),
                x.TypeName,
                x.StatusName,
                x.VerifName,
                CreatedAt = EF.Property<DateTime>(x.m, "_createdAt"),
                PlanId = EF.Property<int>(x.m, "_subscriptionPlanId"),
                x.m.VerifiedAt
            })
            .ToListAsync(cancellationToken);

        // EN: Step 2 — batch-fetch shop counts (single query)
        // VI: Bước 2 — lấy shop counts theo batch (1 query duy nhất)
        var merchantIds = rawItems.Select(r => r.Id).ToList();
        var shopCounts = await _context.Shops
            .Where(s => merchantIds.Contains(EF.Property<Guid>(s, "_merchantId")) && !EF.Property<bool>(s, "_isDeleted"))
            .GroupBy(s => EF.Property<Guid>(s, "_merchantId"))
            .Select(g => new { MerchantId = g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.MerchantId, x => x.Count, cancellationToken);

        var planNames = new[] { "Starter", "Growth", "Pro", "Enterprise" };
        var items = rawItems.Select(r => new AdminMerchantListItemDto(
            r.Id, r.UserId, r.BusinessName, r.TypeName, r.StatusName, r.VerifName,
            shopCounts.GetValueOrDefault(r.Id, 0),
            r.PlanId, planNames.ElementAtOrDefault(r.PlanId) ?? "Starter",
            r.CreatedAt, r.VerifiedAt
        )).ToList();

        return new AdminMerchantListResultDto(
            items,
            totalCount,
            request.Page,
            request.PageSize,
            totalPages);
    }
}
