using Application.Abstractions.Messaging;
using Application.Abstractions.Data;
using Domain.Entities.Forum.ValueObject;
using SharedKernel.Results;
using Microsoft.EntityFrameworkCore;

namespace Application.Features.Api.Forum.Comment.List;

public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
{
    public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
    {
        // 1. 댓글 총 개수 (표시용: 루트+대댓글) & 루트 댓글 개수 (페이지네이션용)
        var total = await db.Comment.AsNoTracking().CountAsync(c => c.PostID == request.PostID && !c.IsDeleted, ct);
        var totalRoots = await db.Comment.AsNoTracking().CountAsync(c => c.PostID == request.PostID && !c.IsDeleted && c.ParentID == null, ct);

        if (totalRoots == 0)
        {
            return new Response(total, 0, []);
        }

        // 비밀글 열람 권한 판별을 위한 게시글 정보 조회
        var post = await db.Post.AsNoTracking().Where(p => p.ID == request.PostID).Select(p => new { p.MemberID, p.BoardID }).FirstOrDefaultAsync(ct);

        var canViewAllSecrets = false;
        if (request.MemberID.HasValue && post is not null)
        {
            var reqMember = await db.Member.AsNoTracking().FirstOrDefaultAsync(x => x.ID == request.MemberID.Value, ct);
            if (reqMember is not null && reqMember.IsAdmin)
            {
                canViewAllSecrets = true;
            }
            else if (reqMember is not null)
            {
                canViewAllSecrets = await db.BoardManager.AsNoTracking().AnyAsync(x => x.BoardID == post.BoardID && x.MemberID == request.MemberID.Value, ct);
            }
        }

        // 2. 루트 댓글 페이지네이션 + 정렬
        var rootQuery = db.Comment.AsNoTracking().Include(c => c.Member).ThenInclude(m => m.MemberGrade).Include(c => c.CommentMention).Where(c => c.PostID == request.PostID && !c.IsDeleted && c.ParentID == null);

        rootQuery = request.Sort switch
        {
            // 인기순
            1 => rootQuery.OrderByDescending(c => c.Likes).ThenByDescending(c => c.ID),

            // 최신순
            _ => rootQuery.OrderByDescending(c => c.ID)
        };

        var roots = await rootQuery.Skip((request.Page - 1) * request.PerPage).Take(request.PerPage).ToListAsync(ct);

        // 3. 루트 댓글의 자식 댓글 일괄 로드
        var rootIDs = roots.Select(c => c.ID).ToHashSet();

        var children = await db.Comment.AsNoTracking()
            .Include(c => c.Member).ThenInclude(m => m.MemberGrade)
            .Include(c => c.CommentMention)
            .Where(c => c.PostID == request.PostID && !c.IsDeleted && c.ParentID != null && rootIDs.Contains(c.ParentID.Value))
            .OrderBy(c => c.ID)
            .ToListAsync(ct);

        var allComments = roots.Concat(children).ToList();

        // 4. 사용자별 상태 벌크 로드
        HashSet<int> likeIDs = [], dislikeIDs = [], reportedIDs = [];

        if (request.MemberID is int memberID)
        {
            var allCommentIds = allComments.Select(c => c.ID).ToHashSet();

            likeIDs = (await db.CommentReaction.AsNoTracking()
                .Where(x => x.PostID == request.PostID && x.MemberID == memberID && x.Reaction == Reaction.Like && allCommentIds.Contains(x.CommentID))
                .Select(x => x.CommentID)
                .ToListAsync(ct)).ToHashSet();

            dislikeIDs = (await db.CommentReaction.AsNoTracking()
                .Where(x => x.PostID == request.PostID && x.MemberID == memberID && x.Reaction == Reaction.Dislike && allCommentIds.Contains(x.CommentID))
                .Select(x => x.CommentID)
                .ToListAsync(ct)).ToHashSet();

            reportedIDs = (await db.CommentReport.AsNoTracking()
                .Where(x => x.PostID == request.PostID && x.MemberID == memberID && allCommentIds.Contains(x.CommentID))
                .Select(x => x.CommentID)
                .ToListAsync(ct)).ToHashSet();
        }

        // 5. CommentItem 매핑
        var itemMap = new Dictionary<int, Response.CommentItem>();
        var rootItems = new List<Response.CommentItem>();

        foreach (var c in allComments)
        {
            var writer = new Response.WriterDto(
                c.Member.ID,
                c.Member.SID,
                c.Member.Name,
                c.Member.Thumb,
                c.Member.Icon,
                c.Member.MemberGrade?.Image,
                c.Member.CreatedAt
            );

            Response.MentionDto? mention = c.CommentMention is not null ? new Response.MentionDto(c.CommentMention.MemberID, c.CommentMention.RawHandle) : null;

            // 비밀글: 본인, 게시글 작성자, 관리자/매니저만 열람 가능
            var content = c.Content;
            if (c.IsSecret && !canViewAllSecrets && request.MemberID != c.MemberID && request.MemberID != post?.MemberID)
            {
                content = "";
            }

            var item = new Response.CommentItem(
                c.ID,
                c.PostID,
                c.MemberID,
                c.ParentID,
                writer,
                mention,
                content,
                c.IsReply,
                c.IsSecret,
                c.Likes,
                c.Dislikes,
                c.Reports,
                c.Replies,
                likeIDs.Contains(c.ID),
                dislikeIDs.Contains(c.ID),
                reportedIDs.Contains(c.ID),
                c.CreatedAt,
                []
            );

            itemMap[c.ID] = item;
        }

        // 6. 트리 빌드
        foreach (var item in itemMap.Values)
        {
            if (item.ParentID is int parentID && itemMap.TryGetValue(parentID, out var parent))
            {
                parent.Children.Add(item);
            }
            else
            {
                rootItems.Add(item);
            }
        }

        return new Response(total, totalRoots, rootItems);
    }
}