using MediatR;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Application.App.Tenants.Targeting;
using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Tenants.Handlers;

/// <summary>
/// 公告分页查询处理器。
/// </summary>
public sealed class GetTenantsAnnouncementsQueryHandler(
    ITenantAnnouncementRepository announcementRepository,
    ITenantAnnouncementReadRepository announcementReadRepository,
    ITenantProvider tenantProvider,
    ICurrentUserAccessor? currentUserAccessor = null,
    IAdminAuthService? adminAuthService = null,
    IMiniAuthService? miniAuthService = null)
    : IRequestHandler<GetTenantsAnnouncementsQuery, PagedResult<TenantAnnouncementDto>>
{
    /// <summary>
    /// 分页查询公告列表。
    /// </summary>
    /// <param name="request">查询条件。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>分页结果。</returns>
    public async Task<PagedResult<TenantAnnouncementDto>> Handle(GetTenantsAnnouncementsQuery request, CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var effectiveAt = request.OnlyEffective == true ? DateTime.UtcNow : (DateTime?)null;

        // 计算分页参数
        var page = request.Page <= 0 ? 1 : request.Page;
        var size = request.PageSize <= 0 ? 20 : request.PageSize;

        // 估算需要查询的数量：考虑到目标受众过滤可能会移除一些记录，
        // 我们查询 3 倍的数量以确保有足够的结果
        var estimatedLimit = page * size * 3;

        // 1. 优化的数据库查询：应用排序和限制
        var announcements = await announcementRepository.SearchAsync(
            tenantId,
            request.Keyword,
            request.Status,
            request.AnnouncementType,
            request.IsActive,
            request.EffectiveFrom,
            request.EffectiveTo,
            effectiveAt,
            orderByPriority: true,      // 在数据库端排序
            limit: estimatedLimit,       // 限制结果数量
            cancellationToken);

        // 2. 内存过滤：ScheduledPublishAt
        if (effectiveAt.HasValue)
        {
            var at = effectiveAt.Value;
            announcements = announcements
                .Where(x => x.ScheduledPublishAt == null || x.ScheduledPublishAt <= at)
                .ToList();
        }

        // 3. 目标受众过滤（在内存中，但数据量已大幅减少）
        var targetContext = await AnnouncementTargetContextFactory.BuildAsync(
            tenantProvider,
            currentUserAccessor,
            adminAuthService,
            miniAuthService,
            cancellationToken);

        var filtered = announcements
            .Where(a => TargetTypeFilter.IsMatch(a, targetContext))
            .ToList();

        // 注意：由于目标受众过滤可能移除记录，filtered.Count 可能小于请求的 size
        // 这是可接受的，因为精确计算总数代价高昂

        // 4. 分页（数据已在数据库层排序，这里只需 Skip/Take）
        var pageItems = filtered
            .Skip((page - 1) * size)
            .Take(size)
            .ToList();

        // 5. 构建已读映射
        var announcementIds = pageItems.Select(x => x.Id).ToArray();
        var userId = targetContext.UserId;

        var readMap = new Dictionary<long, (bool isRead, DateTime? readAt)>();
        if (announcementIds.Length > 0)
        {
            var reads = new List<Domain.Tenants.Entities.TenantAnnouncementRead>();
            if (userId != 0)
            {
                var userReads = await announcementReadRepository.GetByAnnouncementAsync(tenantId, announcementIds, userId, cancellationToken);
                reads.AddRange(userReads);
            }

            var tenantReads = await announcementReadRepository.GetByAnnouncementAsync(tenantId, announcementIds, null, cancellationToken);
            reads.AddRange(tenantReads);

            foreach (var read in reads.OrderByDescending(x => x.ReadAt))
            {
                if (readMap.ContainsKey(read.AnnouncementId) && read.UserId.HasValue)
                {
                    continue;
                }

                readMap[read.AnnouncementId] = (true, read.ReadAt);
            }
        }

        // 6. 映射 DTO 并带上已读状态
        var items = pageItems
            .Select(a =>
            {
                readMap.TryGetValue(a.Id, out var read);
                return a.ToDto(read.isRead, read.readAt);
            })
            .ToList();

        // 注意：由于我们使用了估算的 limit，总数是 filtered.Count 而不是数据库中的实际总数
        // 这是一个权衡：精确的总数需要额外的 COUNT 查询，代价较高
        return new PagedResult<TenantAnnouncementDto>(items, page, size, filtered.Count);
    }
}
