using System.Text.Json;
using Dapper;
using Femto.Common.Infrastructure.DbConnection;
using Femto.Modules.Blog.Application.Queries.GetPosts.Dto;
using MediatR;

namespace Femto.Modules.Blog.Application.Queries.GetPosts;

public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory)
    : IRequestHandler<GetPostsQuery, GetPostsQueryResult>
{
    public async Task<GetPostsQueryResult> Handle(
        GetPostsQuery query,
        CancellationToken cancellationToken
    )
    {
        using var conn = connectionFactory.GetConnection();

        var username = query.Author;
        var authorGuid = query.AuthorId;
        var cursor = query.After;
        var showPrivate = query.CurrentUserId is not null;

        var loadPostsResult = await conn.QueryAsync<LoadPostRow>(
            """
            select 
                blog.post.id as PostId,
                blog.post.content as Content,
                blog.post.posted_on as PostedOn,
                blog.author.username as Username,
                blog.author.id as AuthorId,
                blog.post.possible_reactions as PossibleReactions
            from blog.post
            inner join blog.author on blog.author.id = blog.post.author_id
            where (@username is null or blog.author.username = @username)
            and (@postId is null or blog.post.id = @postId)
            and (@showPrivate or blog.post.is_public = true)
            and (@authorGuid is null or blog.author.id = @authorGuid)
            and (@cursor is null or blog.post.id < @cursor)
            order by blog.post.id desc
            limit @amount
            """,
            new
            {
                username,
                authorGuid,
                cursor,
                amount = query.PostId is not null ? 1 : query.Amount,
                showPrivate,
                postId = query.PostId,
            }
        );

        var posts = loadPostsResult.ToList();

        var postIds = posts.Select(p => p.PostId).ToList();

        var loadMediaResult = await conn.QueryAsync<LoadMediaRow>(
            """
            select 
                pm.url as MediaUrl,
                pm.type as MediaType,
                pm.width as MediaWidth,
                pm.height as MediaHeight,
                pm.post_id as PostId
            from blog.post_media pm where pm.post_id = ANY (@postIds)
            order by pm.ordering
            """,
            new { postIds }
        );

        var media = loadMediaResult.ToList();

        var loadReactionsResult = await conn.QueryAsync<LoadReactionRow>(
            """
            select 
                pr.post_id as PostId,
                a.username as AuthorName,
                pr.emoji as Emoji,
                pr.created_at as CreatedOn
            from blog.post_reaction pr
            join blog.author a on a.id = pr.author_id
            where pr.post_id = ANY (@postIds)
            """,
            new { postIds }
        );

        var reactions = loadReactionsResult.ToList();

        var loadCommentsResult = await conn.QueryAsync<LoadCommentRow>(
            """
            select 
                pc.id as CommentId,
                pc.post_id as PostId,
                pc.content as Content,
                pc.created_at as PostedOn,
                a.username as AuthorName
            from blog.post_comment pc
            join blog.author a on pc.author_id = a.id
            where pc.post_id = ANY (@postIds)
            """,
            new { postIds }
        );

        var comments = loadCommentsResult.ToList();

        return new GetPostsQueryResult(
            posts
                .Select(p => new PostDto(
                    p.PostId,
                    p.Content,
                    media
                        .Where(m => m.PostId == p.PostId)
                        .Select(m => new PostMediaDto(
                            new Uri(m.MediaUrl),
                            m.MediaWidth,
                            m.MediaHeight
                        ))
                        .ToList(),
                    p.PostedOn,
                    new PostAuthorDto(p.AuthorId, p.Username),
                    reactions
                        .Where(r => r.PostId == p.PostId)
                        .Select(r => new PostReactionDto(r.Emoji, r.AuthorName, r.CreatedAt))
                        .ToList(),
                    !string.IsNullOrEmpty(p.PossibleReactions)
                        ? JsonSerializer.Deserialize<IEnumerable<string>>(p.PossibleReactions)!
                        : [],
                    comments
                        .Where(c => c.PostId == p.PostId)
                        .Select(c => new PostCommentDto(c.AuthorName, c.Content, c.PostedOn))
                        .ToList()
                ))
                .ToList()
        );
    }

    internal record LoadPostRow
    {
        public Guid PostId { get; init; }
        public string Content { get; init; }
        public DateTimeOffset PostedOn { get; init; }
        public string Username { get; init; }
        public Guid AuthorId { get; init; }
        public string? PossibleReactions { get; init; }
    }

    internal record LoadMediaRow
    {
        public string MediaUrl { get; init; }
        public string? MediaType { get; init; }
        public int? MediaWidth { get; init; }
        public int? MediaHeight { get; init; }
        public Guid PostId { get; init; }
    }

    internal record LoadReactionRow
    {
        public Guid PostId { get; init; }
        public string AuthorName { get; init; }
        public string Emoji { get; init; }
        public DateTimeOffset CreatedAt { get; init; }
    }

    internal record LoadCommentRow
    {
        public Guid CommentId { get; init; }
        public Guid PostId { get; init; }
        public string Content { get; init; }
        public DateTimeOffset PostedOn { get; init; }
        public string AuthorName { get; init; }
    }
}
