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 orderBy = query.Direction is GetPostsDirection.Backward ? "desc" : "asc";
        var pageFilter = query.Direction is GetPostsDirection.Backward ? "<=" : ">=";
        var username = query.Author;
        var authorGuid = query.AuthorId;
        var cursor = query.From;
        var showPrivate = query.CurrentUserId is not null;

        // lang=sql
        var sql = $$"""
            with page as (
                select blog.post.*, blog.author.username as Username, blog.author.id as AuthorId
                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 (@showPrivate or blog.post.is_public = true)
                and (@authorGuid is null or blog.author.id = @authorGuid)
                and (@cursor is null or blog.post.id {{pageFilter}} @cursor)
                order by blog.post.id {{orderBy}}
                limit @amount
            )
            select
                page.id as PostId,
                page.content as Content,
                blog.post_media.url as MediaUrl,
                blog.post_media.width as MediaWidth,
                blog.post_media.height as MediaHeight,
                page.posted_on as PostedOn,
                page.Username,
                page.AuthorId
                from page
            left join blog.post_media on blog.post_media.post_id = page.id
            order by page.id {{orderBy}}
            """;

        var result = await conn.QueryAsync<QueryResult>(
            sql,
            new
            {
                username,
                authorGuid,
                cursor,
                // load an extra one to take for the cursor
                amount = query.Amount + 1,
                showPrivate,
            }
        );

        var rows = result.ToList();

        var posts = rows.GroupBy(row => row.PostId)
            .Select(group =>
            {
                var postId = group.Key;
                var post = group.First();
                var media = group
                    .Select(row =>
                    {
                        if (row.MediaUrl is not null)
                        {
                            return new PostMediaDto(
                                new Uri(row.MediaUrl),
                                row.MediaHeight,
                                row.MediaHeight
                            );
                        }
                        else
                            return null;
                    })
                    .OfType<PostMediaDto>()
                    .ToList();
                return new PostDto(
                    postId,
                    post.Content,
                    media,
                    post.PostedOn,
                    new PostAuthorDto(post.AuthorId, post.Username)
                );
            })
            .ToList();

        var next = rows.Count >= query.Amount ? rows.LastOrDefault()?.PostId : null;

        return new GetPostsQueryResult(posts, next);
    }

    internal class QueryResult
    {
        public Guid PostId { get; set; }
        public string Content { get; set; }
        public string? MediaUrl { get; set; }
        public string? MediaType { get; set; }
        public int? MediaWidth { get; set; }
        public int? MediaHeight { get; set; }
        public DateTimeOffset PostedOn { get; set; }
        public Guid AuthorId { get; set; }
        public string Username { get; set; }
    }
}
