use axum::{
    extract::{Path, Query},
    http::{HeaderMap, HeaderValue},
    response::IntoResponse,
    Extension, Json,
};
use axum_extra::extract::CookieJar;
use tetratto_core::model::{
    addr::RemoteAddr,
    communities::{Poll, PollVote, Post},
    oauth,
    permissions::FinePermission,
    uploads::{MediaType, MediaUpload},
    ApiReturn, Error,
};
use crate::{
    check_user_blocked_or_private, get_user_from_token,
    image::{save_webp_buffer, JsonMultipart},
    routes::{
        api::v1::{
            CreatePost, CreateRepost, UpdatePostContent, UpdatePostContext, UpdatePostIsOpen,
            VoteInPoll,
        },
        pages::{PaginatedQuery, SearchedQuery},
    },
    State,
};

// maximum file dimensions: 2048x2048px (4 MiB)
pub const MAXIMUM_FILE_SIZE: usize = 4194304;

pub async fn create_request(
    jar: CookieJar,
    headers: HeaderMap,
    Extension(data): Extension<State>,
    JsonMultipart(images, req): JsonMultipart<CreatePost>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreatePosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    if !user.permissions.check(FinePermission::SUPPORTER) {
        if images.len() > 0 {
            // this is currently supporter only until it's been tested better...
            // after it's fully release, file limit will be raised to 8 MiB for supporters,
            // and left at 4 for non-supporters
            return Json(Error::RequiresSupporter.into());
        }
    }

    if images.len() > 4 {
        return Json(
            Error::MiscError("Too many uploads. Please use a maximum of 4".to_string()).into(),
        );
    }

    // get real ip
    let real_ip = headers
        .get(data.0.0.security.real_ip_header.to_owned())
        .unwrap_or(&HeaderValue::from_static(""))
        .to_str()
        .unwrap_or("")
        .to_string();

    // check for ip ban
    if data
        .get_ipban_by_addr(RemoteAddr::from(real_ip.as_str()))
        .await
        .is_ok()
    {
        return Json(Error::NotAllowed.into());
    }

    // create poll
    let poll_id = if let Some(p) = req.poll {
        match data
            .create_poll(Poll::new(
                user.id,
                if let Some(expires) = p.expires {
                    expires
                } else {
                    86400000
                },
                p.option_a,
                p.option_b,
                p.option_c,
                p.option_d,
            ))
            .await
        {
            Ok(p) => p,
            Err(e) => return Json(e.into()),
        }
    } else {
        0
    };

    // ...
    let mut props = Post::new(
        req.content,
        match req.community.parse::<usize>() {
            Ok(x) => x,
            Err(e) => return Json(Error::MiscError(e.to_string()).into()),
        },
        if let Some(rt) = req.replying_to {
            match rt.parse::<usize>() {
                Ok(x) => Some(x),
                Err(e) => return Json(Error::MiscError(e.to_string()).into()),
            }
        } else {
            None
        },
        user.id,
        poll_id,
    );

    if !req.answering.is_empty() {
        // we're answering a question!
        props.context.answering = match req.answering.parse::<usize>() {
            Ok(x) => x,
            Err(e) => return Json(Error::MiscError(e.to_string()).into()),
        };
    } else {
        props.title = req.title;
        props.stack = match req.stack.parse::<usize>() {
            Ok(x) => x,
            Err(e) => return Json(Error::MiscError(e.to_string()).into()),
        };
    }

    // check sizes
    for img in &images {
        if img.len() > MAXIMUM_FILE_SIZE {
            return Json(Error::FileTooLarge.into());
        }
    }

    // create uploads
    for _ in 0..images.len() {
        props.uploads.push(
            match data
                .create_upload(MediaUpload::new(MediaType::Webp, props.owner))
                .await
            {
                Ok(u) => u.id,
                Err(e) => return Json(e.into()),
            },
        );
    }

    // ...
    match data.create_post(props.clone()).await {
        Ok(id) => {
            // write to uploads
            for (i, upload_id) in props.uploads.iter().enumerate() {
                let image = match images.get(i) {
                    Some(img) => img,
                    None => {
                        if let Err(e) = data.delete_upload(*upload_id).await {
                            return Json(e.into());
                        }

                        continue;
                    }
                };

                let upload = match data.get_upload_by_id(*upload_id).await {
                    Ok(u) => u,
                    Err(e) => return Json(e.into()),
                };

                if let Err(e) =
                    save_webp_buffer(&upload.path(&data.0.0).to_string(), image.to_vec(), None)
                {
                    return Json(Error::MiscError(e.to_string()).into());
                }
            }

            // return
            Json(ApiReturn {
                ok: true,
                message: "Post created".to_string(),
                payload: Some(id.to_string()),
            })
        }
        Err(e) => Json(e.into()),
    }
}

pub async fn create_repost_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
    Json(req): Json<CreateRepost>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreatePosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let mut props = Post::repost(
        req.content,
        match req.community.parse::<usize>() {
            Ok(x) => x,
            Err(e) => return Json(Error::MiscError(e.to_string()).into()),
        },
        user.id,
        id,
    );

    props.stack = match req.stack.parse::<usize>() {
        Ok(x) => x,
        Err(e) => return Json(Error::MiscError(e.to_string()).into()),
    };

    // ...
    match data.create_post(props).await {
        Ok(id) => Json(ApiReturn {
            ok: true,
            message: "Post reposted".to_string(),
            payload: Some(id.to_string()),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn delete_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserDeletePosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.fake_delete_post(id, user, true).await {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Post deleted".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn purge_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::ModPurgePosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    if !user.permissions.check(FinePermission::MANAGE_POSTS) {
        return Json(Error::NotAllowed.into());
    }

    match data.delete_post(id, user).await {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Post deleted".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn restore_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::ModDeletePosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    if !user.permissions.check(FinePermission::MANAGE_POSTS) {
        return Json(Error::NotAllowed.into());
    }

    match data.fake_delete_post(id, user, false).await {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Post restored".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn update_content_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
    Json(req): Json<UpdatePostContent>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserEditPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.update_post_content(id, user, req.content).await {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Post updated".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn update_context_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
    Json(req): Json<UpdatePostContext>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserEditPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    // check lengths
    if req.context.tags.len() > 512 {
        return Json(Error::DataTooLong("tags".to_string()).into());
    }

    if req.context.content_warning.len() > 512 {
        return Json(Error::DataTooLong("warning".to_string()).into());
    }

    // ...
    match data.update_post_context(id, user, req.context).await {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Post updated".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn vote_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
    Json(req): Json<VoteInPoll>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserVote) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let post = match data.get_post_by_id(id).await {
        Ok(p) => p,
        Err(e) => return Json(e.into()),
    };

    let poll = match data.get_poll_by_id(post.poll_id).await {
        Ok(p) => p,
        Err(e) => return Json(e.into()),
    };

    // check associated accounts for prior votes
    for id in user.associated {
        if data.get_pollvote_by_owner_poll(id, poll.id).await.is_ok() {
            return Json(
                Error::MiscError(
                    "You've already voted on this poll on a different account".to_string(),
                )
                .into(),
            );
        }
    }

    // ...
    match data
        .create_pollvote(PollVote::new(user.id, poll.id, req.option))
        .await
    {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Vote cast".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

pub async fn update_is_open_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Path(id): Path<usize>,
    Json(req): Json<UpdatePostIsOpen>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserEditPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.update_post_is_open(id, user, req.open).await {
        Ok(_) => Json(ApiReturn {
            ok: true,
            message: "Post updated".to_string(),
            payload: (),
        }),
        Err(e) => Json(e.into()),
    }
}

/// Get posts by the given user.
pub async fn posts_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let other_user = match data.get_user_by_id(id).await {
        Ok(ua) => ua,
        Err(e) => return Json(e.into()),
    };

    check_user_blocked_or_private!(Some(&user), other_user, data, @api);
    match data
        .get_posts_by_user(id, 12, props.page, &Some(user.clone()))
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get posts in the given community.
pub async fn community_posts_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.get_posts_by_community(id, 12, props.page).await {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get replies by the given user.
pub async fn replies_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let other_user = match data.get_user_by_id(id).await {
        Ok(ua) => ua,
        Err(e) => return Json(e.into()),
    };

    check_user_blocked_or_private!(Some(&user), other_user, data, @api);
    match data
        .get_replies_by_user(id, 12, props.page, &Some(user.clone()))
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get posts (with media) by the given user.
pub async fn posts_with_media_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let other_user = match data.get_user_by_id(id).await {
        Ok(ua) => ua,
        Err(e) => return Json(e.into()),
    };

    check_user_blocked_or_private!(Some(&user), other_user, data, @api);
    match data
        .get_media_posts_by_user(id, 12, props.page, &Some(user.clone()))
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get posts (searched) by the given user.
pub async fn posts_searched_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<SearchedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let other_user = match data.get_user_by_id(id).await {
        Ok(ua) => ua,
        Err(e) => return Json(e.into()),
    };

    check_user_blocked_or_private!(Some(&user), other_user, data, @api);
    match data
        .get_posts_by_user_searched(id, 12, props.page, &props.text, &Some(&user))
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get all posts (searched).
pub async fn all_posts_searched_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(props): Query<SearchedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    if !user.permissions.check(FinePermission::SUPPORTER) {
        return Json(Error::RequiresSupporter.into());
    }

    match data.get_posts_searched(12, props.page, &props.text).await {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get all posts (from user communities).
pub async fn from_communities_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data
        .get_posts_from_user_communities(user.id, 12, props.page)
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get all posts (from stack).
pub async fn from_stack_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    let stack = match data.get_stack_by_id(id).await {
        Ok(s) => s,
        Err(e) => return Json(e.into()),
    };

    if stack.owner != user.id && !user.permissions.check(FinePermission::MANAGE_STACKS) {
        return Json(Error::NotAllowed.into());
    }

    match data
        .get_posts_from_stack(id, 12, props.page, stack.sort)
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get all posts (by likes).
pub async fn popular_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.get_popular_posts(12, props.page, 604_800_000).await {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get all posts (from any community).
pub async fn all_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data
        .get_latest_posts(12, props.page, &Some(user.clone()))
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get all posts (from following).
pub async fn following_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data
        .get_posts_from_user_following(user.id, 12, props.page)
        .await
    {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get a single post.
pub async fn get_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    if get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts).is_none() {
        return Json(Error::NotAllowed.into());
    }

    match data.get_post_by_id(id).await {
        Ok(p) => Json(ApiReturn {
            ok: true,
            message: "Success".to_string(),
            payload: Some(p),
        }),
        Err(e) => Json(e.into()),
    }
}

/// Get replies for the given post.
pub async fn post_replies_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.get_replies_by_post(id, 12, props.page).await {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get reposts for the given post.
pub async fn reposts_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.get_reposts_by_quoting(id, 12, props.page).await {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}

/// Get quotes for the given post.
pub async fn quotes_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
    Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
    let data = &(data.read().await).0;
    let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadPosts) {
        Some(ua) => ua,
        None => return Json(Error::NotAllowed.into()),
    };

    match data.get_quoting_posts_by_quoting(id, 12, props.page).await {
        Ok(posts) => {
            let ignore_users = crate::ignore_users_gen!(user!, #data);
            Json(ApiReturn {
                ok: true,
                message: "Success".to_string(),
                payload: match data
                    .fill_posts_with_community(posts, user.id, &ignore_users, &Some(user.clone()))
                    .await
                {
                    Ok(l) => data.posts_owner_filter(
                        &data.posts_muted_phrase_filter(&l, Some(&user.settings.muted)),
                    ),
                    Err(e) => return Json(e.into()),
                },
            })
        }
        Err(e) => Json(e.into()),
    }
}