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

namespace Application.Features.Api.Forum.Post.Search;

public sealed class Handler(IAppDbContext db, IBoardPermissionService permissionService) : IQueryHandler<Query, Response>
{
    public async Task<Response> Handle(Query request, CancellationToken ct)
    {
        // 공통 기본 쿼리
        var baseQuery = db.Post.AsNoTracking().Include(c => c.Board).Include(c => c.BoardPrefix).Where(c => !c.IsDeleted).AsQueryable();

        if (request.BoardID.HasValue)
        {
            baseQuery = baseQuery.Where(c => c.BoardID == request.BoardID.Value);
        }

        if (!string.IsNullOrWhiteSpace(request.Keyword))
        {
            var kw = request.Keyword.Trim();
            baseQuery = request.Search switch
            {
                0 => baseQuery.Where(c => c.Subject.Contains(kw)),
                1 => baseQuery.Where(c => c.Content.Contains(kw)),
                2 => baseQuery.Where(c => (c.Name != null && c.Name.Contains(kw)) || (c.SID != null && c.SID.Contains(kw))),
                _ => baseQuery.Where(c => c.Subject.Contains(kw) || c.Content.Contains(kw))
            };
        }

        if (!string.IsNullOrWhiteSpace(request.StartAt) && DateTime.TryParse(request.StartAt, out var startDate))
        {
            baseQuery = baseQuery.Where(c => c.CreatedAt >= startDate);
        }

        if (!string.IsNullOrWhiteSpace(request.EndAt) && DateTime.TryParse(request.EndAt, out var endDate))
        {
            baseQuery = baseQuery.Where(c => c.CreatedAt <= endDate.AddDays(1));
        }

        if (request.IsSecret == true)
        {
            baseQuery = baseQuery.Where(c => c.IsSecret);
        }

        if (request.IsReply == true)
        {
            baseQuery = baseQuery.Where(c => c.IsReply);
        }

        if (request.IsDeleted == true)
        {
            baseQuery = baseQuery.Where(c => c.IsDeleted);
        }

        var exceptSpeaker = false;
        var exceptNotice = false;

        if (request.BoardID.HasValue)
        {
            var boardMeta = await db.BoardMeta.AsNoTracking().FirstOrDefaultAsync(x => x.BoardID == request.BoardID.Value, ct);
            if (boardMeta is not null)
            {
                exceptSpeaker = boardMeta.List.ExceptSpeaker;
                exceptNotice = boardMeta.List.ExceptNotice;

                // QnA(1:1 문의) 게시판: 본인 글만 표시 (관리자·매니저 제외)
                if (boardMeta.List.Layout == BoardLayout.QnA && request.MemberID.HasValue)
                {
                    var member = await db.Member.AsNoTracking().FirstOrDefaultAsync(x => x.ID == request.MemberID.Value, ct);
                    if (member is not null && !member.IsAdmin)
                    {
                        var mgr = await permissionService.GetBoardManagerAsync(request.BoardID.Value, member.ID, ct);
                        if (mgr is null)
                        {
                            baseQuery = baseQuery.Where(c => c.MemberID == request.MemberID.Value);
                        }
                    }
                }
            }
        }

        // ── 1) Speaker (전체공지) 조회: 1페이지 && ExceptSpeaker가 아닐 때 ──
        var speakerList = new List<Response.Row>();
        if (request.Page == 1 && !exceptSpeaker)
        {
            var speakers = await baseQuery
                .Where(c => c.IsSpeaker)
                .OrderByDescending(c => c.ID)
                .Select(c => new
                {
                    c.ID,
                    c.BoardID,
                    BoardName = c.Board.Name,
                    c.BoardPrefixID,
                    BoardPrefix = c.BoardPrefix != null ? new { c.BoardPrefix.ID, c.BoardPrefix.BoardID, c.BoardPrefix.Name, c.BoardPrefix.Color, c.BoardPrefix.Posts } : null,
                    c.Subject,
                    c.Thumbnail,
                    c.Name,
                    c.SID,
                    c.IsNotice,
                    c.IsSecret,
                    c.IsReply,
                    c.IsSpeaker,
                    c.IsAnonymous,
                    c.IsDeleted,
                    c.Views,
                    c.Likes,
                    c.Dislikes,
                    c.Comments,
                    c.Images,
                    c.Medias,
                    c.Files,
                    c.Tags,
                    c.UpdatedAt,
                    c.CreatedAt
                })
                .ToListAsync(ct);

            speakerList = [..speakers.Select(c => new Response.Row(
                Num: 0,
                c.ID, c.BoardID, c.BoardName,
                c.BoardPrefixID,
                c.BoardPrefix != null ? new Response.BoardPrefixData(c.BoardPrefix.ID, c.BoardPrefix.BoardID, c.BoardPrefix.Name, c.BoardPrefix.Color, c.BoardPrefix.Posts) : null,
                c.Subject, c.Thumbnail,
                c.Name, c.SID, c.IsNotice, c.IsSecret, c.IsReply, c.IsSpeaker,
                c.IsAnonymous, c.IsDeleted, c.Views, c.Likes, c.Dislikes, c.Comments,
                c.Images, c.Medias, c.Files, c.Tags, c.UpdatedAt, c.CreatedAt
            ))];
        }

        // ── 2) Notice (게시판 공지) 조회: 1페이지 && ExceptNotice가 아닐 때 ──
        var noticeList = new List<Response.Row>();
        if (request.Page == 1 && !exceptNotice)
        {
            var notices = await baseQuery
                .Where(c => c.IsNotice && !c.IsSpeaker)
                .OrderByDescending(c => c.ID)
                .Select(c => new
                {
                    c.ID,
                    c.BoardID,
                    BoardName = c.Board.Name,
                    c.BoardPrefixID,
                    BoardPrefix = c.BoardPrefix != null ? new { c.BoardPrefix.ID, c.BoardPrefix.BoardID, c.BoardPrefix.Name, c.BoardPrefix.Color, c.BoardPrefix.Posts } : null,
                    c.Subject,
                    c.Thumbnail,
                    c.Name,
                    c.SID,
                    c.IsNotice,
                    c.IsSecret,
                    c.IsReply,
                    c.IsSpeaker,
                    c.IsAnonymous,
                    c.IsDeleted,
                    c.Views,
                    c.Likes,
                    c.Dislikes,
                    c.Comments,
                    c.Images,
                    c.Medias,
                    c.Files,
                    c.Tags,
                    c.UpdatedAt,
                    c.CreatedAt
                })
                .ToListAsync(ct);

            noticeList = [..notices.Select(c => new Response.Row(
                Num: 0,
                c.ID, c.BoardID, c.BoardName,
                c.BoardPrefixID,
                c.BoardPrefix != null ? new Response.BoardPrefixData(c.BoardPrefix.ID, c.BoardPrefix.BoardID, c.BoardPrefix.Name, c.BoardPrefix.Color, c.BoardPrefix.Posts) : null,
                c.Subject, c.Thumbnail,
                c.Name, c.SID, c.IsNotice, c.IsSecret, c.IsReply, c.IsSpeaker,
                c.IsAnonymous, c.IsDeleted, c.Views, c.Likes, c.Dislikes, c.Comments,
                c.Images, c.Medias, c.Files, c.Tags, c.UpdatedAt, c.CreatedAt
            ))];
        }

        // ── 3) 일반 글 조회: 공지 제외 + 페이지네이션 ──
        var listQuery = baseQuery.Where(c => !c.IsNotice && !c.IsSpeaker);

        // IsNotice 필터가 명시적으로 true인 경우 (관리 목적 등) 공지만 조회
        if (request.IsNotice == true)
        {
            listQuery = baseQuery.Where(c => c.IsNotice);
        }

        if (request.BoardPrefixID.HasValue)
        {
            listQuery = listQuery.Where(c => c.BoardPrefixID == request.BoardPrefixID.Value);
        }

        listQuery = request.Sort switch
        {
            1 => listQuery.OrderByDescending(c => c.Views),
            2 => listQuery.OrderByDescending(c => c.Comments),
            3 => listQuery.OrderByDescending(c => c.Likes),
            _ => listQuery.OrderByDescending(c => c.ID)
        };

        var total = await listQuery.CountAsync(ct);

        var list = await listQuery
            .Skip((request.Page - 1) * request.PerPage)
            .Take(request.PerPage)
            .Select(c => new
            {
                c.ID,
                c.BoardID,
                BoardName = c.Board.Name,
                c.BoardPrefixID,
                BoardPrefix = c.BoardPrefix != null ? new { c.BoardPrefix.ID, c.BoardPrefix.BoardID, c.BoardPrefix.Name, c.BoardPrefix.Color, c.BoardPrefix.Posts } : null,
                c.Subject,
                c.Thumbnail,
                c.Name,
                c.SID,
                c.IsNotice,
                c.IsSecret,
                c.IsReply,
                c.IsSpeaker,
                c.IsAnonymous,
                c.IsDeleted,
                c.Views,
                c.Likes,
                c.Dislikes,
                c.Comments,
                c.Images,
                c.Medias,
                c.Files,
                c.Tags,
                c.UpdatedAt,
                c.CreatedAt
            })
            .ToListAsync(ct);

        var startNum = total - ((request.Page - 1) * request.PerPage);

        return new Response(
            total,
            speakerList,
            noticeList,
            [..list.Select((c, i) => new Response.Row(
                Num: startNum - i,
                c.ID,
                c.BoardID,
                c.BoardName,
                c.BoardPrefixID,
                c.BoardPrefix != null ? new Response.BoardPrefixData(c.BoardPrefix.ID, c.BoardPrefix.BoardID, c.BoardPrefix.Name, c.BoardPrefix.Color, c.BoardPrefix.Posts) : null,
                c.Subject,
                c.Thumbnail,
                c.Name,
                c.SID,
                c.IsNotice,
                c.IsSecret,
                c.IsReply,
                c.IsSpeaker,
                c.IsAnonymous,
                c.IsDeleted,
                c.Views,
                c.Likes,
                c.Dislikes,
                c.Comments,
                c.Images,
                c.Medias,
                c.Files,
                c.Tags,
                c.UpdatedAt,
                c.CreatedAt
            ))]
        );
    }
}