use super::{render_error, PaginatedQuery, RepostsQuery, SearchedQuery};
use crate::{
    assets::initial_context, check_user_blocked_or_private, get_lang, get_user_from_token, State,
};
use axum::{
    Extension,
    extract::{Path, Query},
    response::{Html, IntoResponse},
};
use axum_extra::extract::CookieJar;
use serde::Deserialize;
use tera::Context;
use tetratto_core::model::{
    auth::User,
    communities::{Community, CommunityReadAccess},
    communities_permissions::CommunityPermission,
    permissions::FinePermission,
    Error,
};

macro_rules! check_permissions {
    ($community:ident, $jar:ident, $data:ident, $user:ident) => {{
        let mut is_member: bool = false;
        let mut can_manage_pins: bool = false;
        let mut can_manage_communities: bool = false;

        if let Some(ref ua) = $user {
            if ua
                .permissions
                .check(tetratto_core::model::permissions::FinePermission::MANAGE_COMMUNITIES)
            {
                can_manage_communities = true;
            }

            if let Ok(membership) = $data
                .0
                .get_membership_by_owner_community(ua.id, $community.id)
                .await
            {
                if membership.role.check_banned() {
                    return Err(Html(
                        render_error(Error::NotAllowed, &$jar, &$data, &$user).await,
                    ));
                } else if membership.role.check_member() {
                    is_member = true;
                }

                if membership.role.check(
                    tetratto_core::model::communities_permissions::CommunityPermission::MANAGE_PINS,
                ) {
                    can_manage_pins = true;
                }
            }
        }

        match $community.read_access {
            CommunityReadAccess::Joined => {
                if !can_manage_communities {
                    if !is_member {
                        (false, can_manage_pins)
                    } else {
                        (true, can_manage_pins)
                    }
                } else {
                    (true, true)
                }
            }
            _ => (true, can_manage_pins),
        }
    }};
}

macro_rules! community_context_bools {
    ($data:ident, $user:ident, $community:ident) => {{
        let membership = if let Some(ref ua) = $user {
            match $data
                .0
                .get_membership_by_owner_community(ua.id, $community.id)
                .await
            {
                Ok(m) => Some(m),
                Err(_) => None,
            }
        } else {
            None
        };

        let is_owner = if let Some(ref ua) = $user {
            ua.id == $community.owner
        } else {
            false
        };

        let is_joined = if let Some(ref membership) = membership {
            membership.role.check_member()
        } else {
            false
        };

        let is_pending = if let Some(ref membership) = membership {
            membership.role.check(CommunityPermission::REQUESTED)
        } else {
            false
        };

        let can_post = if let Some(ref ua) = $user {
            $data.0.check_can_post(&$community, ua.id).await
        } else {
            false
        };

        let can_manage_posts = if let Some(ref membership) = membership {
            membership.role.check(CommunityPermission::MANAGE_POSTS)
        } else {
            false
        };

        let can_manage_community = if let Some(ref membership) = membership {
            membership.role.check(CommunityPermission::MANAGE_COMMUNITY)
        } else {
            false
        };

        let can_manage_roles = if let Some(ref membership) = membership {
            membership.role.check(CommunityPermission::MANAGE_ROLES)
        } else {
            false
        };

        let can_manage_questions = if let Some(ref membership) = membership {
            membership.role.check(CommunityPermission::MANAGE_QUESTIONS)
        } else {
            false
        };

        (
            is_owner,
            is_joined,
            is_pending,
            can_post,
            can_manage_posts,
            can_manage_community,
            can_manage_roles,
            can_manage_questions,
        )
    }};
}

/// `/communities`
pub async fn list_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
    let data = data.read().await;
    let user = match get_user_from_token!(jar, data.0) {
        Some(ua) => ua,
        None => {
            return Err(Html(
                render_error(Error::NotAllowed, &jar, &data, &None).await,
            ));
        }
    };

    let list = match data.0.get_memberships_by_owner(user.id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let popular_list = match data.0.get_popular_communities().await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let mut communities: Vec<Community> = Vec::new();
    for membership in &list {
        match data
            .0
            .get_community_by_id_no_void(membership.community)
            .await
        {
            Ok(c) => communities.push(c),
            Err(_) => {
                // delete membership; community doesn't exist
                if let Err(e) = data.0.delete_membership(membership.id, &user).await {
                    return Err(Html(render_error(e, &jar, &data, &Some(user)).await));
                }

                continue;
            }
        }
    }

    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &Some(user)).await;

    context.insert("list", &communities);
    context.insert("popular_list", &popular_list);

    // return
    Ok(Html(
        data.1.render("communities/list.html", &context).unwrap(),
    ))
}

/// `/communities/search`
pub async fn search_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(req): Query<SearchedQuery>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = match get_user_from_token!(jar, data.0) {
        Some(ua) => ua,
        None => {
            return Err(Html(
                render_error(Error::NotAllowed, &jar, &data, &None).await,
            ));
        }
    };

    let communities = match data
        .0
        .get_communities_searched(&req.text, 12, req.page)
        .await
    {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &Some(user)).await;

    context.insert("list", &communities);
    context.insert("page", &req.page);
    context.insert("text", &req.text);

    // return
    Ok(Html(
        data.1.render("communities/search.html", &context).unwrap(),
    ))
}

#[derive(Deserialize)]
pub struct CreatePostProps {
    #[serde(default)]
    pub community: usize,
    #[serde(default)]
    pub from_draft: usize,
    #[serde(default)]
    pub quote: usize,
}

/// `/communities/intents/post`
pub async fn create_post_request(
    jar: CookieJar,
    Extension(data): Extension<State>,
    Query(props): Query<CreatePostProps>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = match get_user_from_token!(jar, data.0) {
        Some(ua) => ua,
        None => {
            return Err(Html(
                render_error(Error::NotAllowed, &jar, &data, &None).await,
            ));
        }
    };

    let memberships = match data.0.get_memberships_by_owner(user.id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let mut communities: Vec<Community> = Vec::new();
    for membership in memberships {
        if membership.community == data.0.0.town_square {
            // we already pulled the town square
            continue;
        }

        let community = match data.0.get_community_by_id(membership.community).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
        };

        communities.push(community)
    }

    // get draft
    let draft = if props.from_draft != 0 {
        match data.0.get_draft_by_id(props.from_draft).await {
            Ok(d) => {
                // drafts can only be used by their owner
                if d.owner == user.id { Some(d) } else { None }
            }
            Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
        }
    } else {
        None
    };

    let drafts = match data.0.get_drafts_by_user_all(user.id).await {
        Ok(l) => l,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    // get quoting
    let quoting = if props.quote != 0 {
        match data.0.get_post_by_id(props.quote).await {
            Ok(q) => Some((
                match data.0.get_user_by_id(q.owner).await {
                    Ok(ua) => ua,
                    Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
                },
                q,
            )),
            Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
        }
    } else {
        None
    };

    // ...
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &Some(user)).await;

    context.insert("draft", &draft);
    context.insert("drafts", &drafts);
    context.insert("quoting", &quoting);
    context.insert("communities", &communities);
    context.insert("selected_community", &props.community);

    // return
    Ok(Html(
        data.1
            .render("communities/create_post.html", &context)
            .unwrap(),
    ))
}

pub fn community_context(
    context: &mut Context,
    community: &Community,
    is_owner: bool,
    is_joined: bool,
    is_pending: bool,
    can_post: bool,
    can_read: bool,
    can_manage_posts: bool,
    can_manage_community: bool,
    can_manage_roles: bool,
    can_manage_questions: bool,
) {
    context.insert("community", &community);
    context.insert("is_owner", &is_owner);
    context.insert("is_joined", &is_joined);
    context.insert("is_pending", &is_pending);
    context.insert("can_post", &can_post);
    context.insert("can_read", &can_read);
    context.insert("can_manage_posts", &can_manage_posts);
    context.insert("can_manage_community", &can_manage_community);
    context.insert("can_manage_roles", &can_manage_roles);
    context.insert("can_manage_questions", &can_manage_questions);
}

/// `/community/{title}`
pub async fn feed_request(
    jar: CookieJar,
    Path(title): Path<String>,
    Query(props): Query<PaginatedQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = get_user_from_token!(jar, data.0);

    let community = match data.0.get_community_by_title(&title.to_lowercase()).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    if community.id == 0 {
        // don't show page for void community
        return Err(Html(
            render_error(
                Error::GeneralNotFound("community".to_string()),
                &jar,
                &data,
                &user,
            )
            .await,
        ));
    }

    // check permissions
    let (can_read, _) = check_permissions!(community, jar, data, user);

    // ...
    let ignore_users = crate::ignore_users_gen!(user, data);

    let feed = match data
        .0
        .get_posts_by_community(community.id, 12, props.page)
        .await
    {
        Ok(p) => match data.0.fill_posts(p, &ignore_users, &user).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    let pinned = match data.0.get_pinned_posts_by_community(community.id).await {
        Ok(p) => match data.0.fill_posts(p, &ignore_users, &user).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &user).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, user, community);

    context.insert("feed", &feed);
    context.insert("pinned", &pinned);
    context.insert("page", &props.page);
    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(
        data.1.render("communities/feed.html", &context).unwrap(),
    ))
}

/// `/community/{title}/questions`
pub async fn questions_request(
    jar: CookieJar,
    Path(title): Path<String>,
    Query(props): Query<PaginatedQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = get_user_from_token!(jar, data.0);

    let community = match data.0.get_community_by_title(&title.to_lowercase()).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    if community.id == 0 {
        // don't show page for void community
        return Err(Html(
            render_error(
                Error::GeneralNotFound("community".to_string()),
                &jar,
                &data,
                &user,
            )
            .await,
        ));
    }

    if !community.context.enable_questions {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &user).await,
        ));
    }

    // check permissions
    let (can_read, _) = check_permissions!(community, jar, data, user);

    // ...
    let ignore_users = crate::ignore_users_gen!(user, data);

    let feed = match data
        .0
        .get_questions_by_community(community.id, 12, props.page)
        .await
    {
        Ok(p) => match data.0.fill_questions(p, &ignore_users).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &user).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, user, community);

    context.insert("feed", &feed);
    context.insert("page", &props.page);
    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(
        data.1
            .render("communities/questions.html", &context)
            .unwrap(),
    ))
}

/// `/community/{id}/manage`
pub async fn settings_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = match get_user_from_token!(jar, data.0) {
        Some(ua) => ua,
        None => {
            return Err(Html(
                render_error(Error::NotAllowed, &jar, &data, &None).await,
            ));
        }
    };

    let community = match data.0.get_community_by_id_no_void(id).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let membership = match data
        .0
        .get_membership_by_owner_community(user.id, community.id)
        .await
    {
        Ok(m) => m,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    if user.id != community.owner
        && !user.permissions.check(FinePermission::MANAGE_COMMUNITIES)
        && !membership.role.check(CommunityPermission::MANAGE_COMMUNITY)
    {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &None).await,
        ));
    }

    let channels = match data.0.get_channels_by_community(community.id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let can_manage_channels = membership.role.check(CommunityPermission::MANAGE_CHANNELS)
        | user.permissions.check(FinePermission::MANAGE_CHANNELS);

    let emojis = match data.0.get_emojis_by_community(community.id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let can_manage_emojis = membership.role.check(CommunityPermission::MANAGE_EMOJIS)
        | user.permissions.check(FinePermission::MANAGE_EMOJIS);

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &Some(user)).await;

    context.insert("community", &community);

    context.insert("can_manage_channels", &can_manage_channels);
    context.insert("channels", &channels);

    context.insert("can_manage_emojis", &can_manage_emojis);
    context.insert("emojis", &emojis);

    // return
    Ok(Html(
        data.1
            .render("communities/settings.html", &context)
            .unwrap(),
    ))
}

/// `/post/{id}`
pub async fn post_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Query(props): Query<PaginatedQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = get_user_from_token!(jar, data.0);

    let post = match data.0.get_post_by_id(id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    if post.is_deleted {
        // act like the post doesn't exist (if missing MANAGE_POSTS)
        if let Some(ref ua) = user {
            if !ua.permissions.check(FinePermission::MANAGE_POSTS) {
                return Err(Html(
                    render_error(
                        Error::GeneralNotFound("post".to_string()),
                        &jar,
                        &data,
                        &user,
                    )
                    .await,
                ));
            }
        } else {
            return Err(Html(
                render_error(
                    Error::GeneralNotFound("post".to_string()),
                    &jar,
                    &data,
                    &user,
                )
                .await,
            ));
        }
    }

    let community = match data.0.get_community_by_id(post.community).await {
        Ok(c) => c,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    let ignore_users = crate::ignore_users_gen!(user, data);

    // ...
    let owner = match data.0.get_user_by_id(post.owner).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    if owner.permissions.check_banned() {
        if let Some(ref ua) = user {
            if !ua.permissions.check(FinePermission::MANAGE_POSTS) {
                return Err(Html(
                    render_error(Error::NotAllowed, &jar, &data, &user).await,
                ));
            }
        } else {
            return Err(Html(
                render_error(Error::NotAllowed, &jar, &data, &user).await,
            ));
        }
    }

    // check if the user has a private account OR if we're blocked...
    // don't do this check if we're the owner of the post this post is replying to
    if let Some(ref ua) = user {
        if let Some(replying) = post.replying_to {
            if replying != 0 {
                let replying_to = match data.0.get_post_by_id(replying).await {
                    Ok(r) => r,
                    Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
                };

                if replying_to.owner != ua.id {
                    check_user_blocked_or_private!(user, owner, data, jar);
                }
            } else {
                check_user_blocked_or_private!(user, owner, data, jar);
            }
        }
    } else {
        check_user_blocked_or_private!(user, owner, data, jar);
    }

    // check repost
    let reposting = data.0.get_post_reposting(&post, &ignore_users, &user).await;

    // check question
    let question = match data.0.get_post_question(&post, &ignore_users).await {
        Ok(q) => q,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // check permissions
    let (can_read, can_manage_pins) = check_permissions!(community, jar, data, user);

    if !can_read {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &user).await,
        ));
    }

    // ...
    let ignore_users = crate::ignore_users_gen!(user, data);

    let feed = match data.0.get_post_comments(post.id, 12, props.page).await {
        Ok(p) => match data.0.fill_posts(p, &ignore_users, &user).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &user).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, user, community);

    context.insert("post", &post);
    context.insert("reposting", &reposting);
    context.insert("question", &question);
    context.insert("replies", &feed);
    context.insert("page", &props.page);
    context.insert(
        "owner",
        &data
            .0
            .get_user_by_id(post.owner)
            .await
            .unwrap_or(User::deleted()),
    );
    context.insert(
        "post_context_serde",
        &serde_json::to_string(&post.context)
            .unwrap()
            .replace("\"", "\\\""),
    );
    context.insert("can_manage_pins", &can_manage_pins);

    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(data.1.render("post/post.html", &context).unwrap()))
}

/// `/post/{id}/reposts`
pub async fn reposts_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Query(props): Query<RepostsQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = get_user_from_token!(jar, data.0);

    let post = match data.0.get_post_by_id(id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    let community = match data.0.get_community_by_id(post.community).await {
        Ok(c) => c,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    let ignore_users = crate::ignore_users_gen!(user, data);

    // ...
    let owner = match data.0.get_user_by_id(post.owner).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    check_user_blocked_or_private!(user, owner, data, jar);

    // check repost
    let reposting = data.0.get_post_reposting(&post, &ignore_users, &user).await;

    // check question
    let question = match data.0.get_post_question(&post, &ignore_users).await {
        Ok(q) => q,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // check permissions
    let (can_read, _) = check_permissions!(community, jar, data, user);

    if !can_read {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &user).await,
        ));
    }

    // ...
    let ignore_users = crate::ignore_users_gen!(user, data);

    let list = if props.quotes {
        match data
            .0
            .get_quoting_posts_by_quoting(post.id, 12, props.page)
            .await
        {
            Ok(p) => match data.0.fill_posts(p, &ignore_users, &user).await {
                Ok(p) => p,
                Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
            },
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        }
    } else {
        match data.0.get_reposts_by_quoting(post.id, 12, props.page).await {
            Ok(p) => match data.0.fill_posts(p, &ignore_users, &user).await {
                Ok(p) => p,
                Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
            },
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        }
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &user).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, user, community);

    context.insert("post", &post);
    context.insert("reposting", &reposting);
    context.insert("question", &question);
    context.insert("list", &list);
    context.insert("page", &props.page);
    context.insert(
        "owner",
        &data
            .0
            .get_user_by_id(post.owner)
            .await
            .unwrap_or(User::deleted()),
    );

    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(
        data.1
            .render(
                if props.quotes {
                    "post/quotes.html"
                } else {
                    "post/reposts.html"
                },
                &context,
            )
            .unwrap(),
    ))
}

/// `/post/{id}/likes`
pub async fn likes_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Query(props): Query<RepostsQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = match get_user_from_token!(jar, data.0) {
        Some(ua) => ua,
        None => {
            return Err(Html(
                render_error(Error::NotAllowed, &jar, &data, &None).await,
            ));
        }
    };

    let post = match data.0.get_post_by_id(id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let community = match data.0.get_community_by_id(post.community).await {
        Ok(c) => c,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    let ignore_users = crate::ignore_users_gen!(user!, data);

    // ...
    let ua = Some(user.clone());
    let membership = data
        .0
        .get_membership_by_owner_community(user.id, community.id)
        .await
        .unwrap();

    if user.id != post.owner
        && user.id != community.owner
        && !membership.role.check(CommunityPermission::MANAGE_POSTS)
        && !user.permissions.check(FinePermission::MANAGE_POSTS)
    {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &None).await,
        ));
    }

    // check repost
    let reposting = data
        .0
        .get_post_reposting(&post, &ignore_users, &Some(user.clone()))
        .await;

    // check question
    let question = match data.0.get_post_question(&post, &ignore_users).await {
        Ok(q) => q,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    // check permissions
    let (can_read, _) = check_permissions!(community, jar, data, ua);

    if !can_read {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &Some(user)).await,
        ));
    }

    // ...
    let ignore_users = crate::ignore_users_gen!(user!, data);

    let list = match if user.permissions.check(FinePermission::MANAGE_REACTIONS) {
        // all reactions
        data.0.get_reactions_by_asset(post.id, 12, props.page).await
    } else {
        // only likes
        data.0
            .get_likes_reactions_by_asset(post.id, 12, props.page)
            .await
    } {
        Ok(p) => match data.0.fill_reactions(&p, ignore_users).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &Some(user)).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, ua, community);

    context.insert("post", &post);
    context.insert("question", &question);
    context.insert("reposting", &reposting);
    context.insert("list", &list);
    context.insert("page", &props.page);
    context.insert(
        "owner",
        &data
            .0
            .get_user_by_id(post.owner)
            .await
            .unwrap_or(User::deleted()),
    );

    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(data.1.render("post/likes.html", &context).unwrap()))
}

/// `/community/{title}/members`
pub async fn members_request(
    jar: CookieJar,
    Path(title): Path<String>,
    Query(props): Query<PaginatedQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = get_user_from_token!(jar, data.0);

    let community = match data.0.get_community_by_title(&title.to_lowercase()).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    if community.id == 0 {
        // don't show page for void community
        return Err(Html(
            render_error(
                Error::GeneralNotFound("community".to_string()),
                &jar,
                &data,
                &user,
            )
            .await,
        ));
    }

    // check permissions
    let (can_read, _) = check_permissions!(community, jar, data, user);

    // ...
    let list = match data
        .0
        .get_memberships_by_community(community.id, community.owner, 12, props.page)
        .await
    {
        Ok(p) => match data.0.fill_users(p).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // get community owner
    let owner = match data.0.get_user_by_id(community.owner).await {
        Ok(ua) => ua,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &user).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, user, community);

    context.insert("list", &list);
    context.insert("page", &props.page);
    context.insert("owner", &owner);
    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(
        data.1.render("communities/members.html", &context).unwrap(),
    ))
}

/// `/question/{id}`
pub async fn question_request(
    jar: CookieJar,
    Path(id): Path<usize>,
    Query(props): Query<PaginatedQuery>,
    Extension(data): Extension<State>,
) -> impl IntoResponse {
    let data = data.read().await;
    let user = get_user_from_token!(jar, data.0);

    let question = match data.0.get_question_by_id(id).await {
        Ok(p) => p,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    let community = match data.0.get_community_by_id(question.community).await {
        Ok(c) => c,
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    let has_answered = if let Some(ref ua) = user {
        data.0
            .get_post_by_owner_question(ua.id, question.id)
            .await
            .is_ok()
    } else {
        false
    };

    let is_sender = if let Some(ref ua) = user {
        ua.id == question.owner
    } else {
        false
    };

    // check permissions
    let (can_read, _) = check_permissions!(community, jar, data, user);

    if !can_read && !is_sender {
        return Err(Html(
            render_error(Error::NotAllowed, &jar, &data, &user).await,
        ));
    }

    // ...
    let ignore_users = crate::ignore_users_gen!(user, data);

    let feed = match data
        .0
        .get_posts_by_question(question.id, 12, props.page)
        .await
    {
        Ok(p) => match data.0.fill_posts(p, &ignore_users, &user).await {
            Ok(p) => p,
            Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
        },
        Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
    };

    // init context
    let lang = get_lang!(jar, data.0);
    let mut context = initial_context(&data.0.0, lang, &user).await;

    let (
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    ) = community_context_bools!(data, user, community);

    context.insert("question", &question);
    context.insert("replies", &feed);
    context.insert("page", &props.page);
    context.insert(
        "owner",
        &data
            .0
            .get_user_by_id(question.owner)
            .await
            .unwrap_or(User::deleted()),
    );
    context.insert("has_answered", &has_answered);

    community_context(
        &mut context,
        &community,
        is_owner,
        is_joined,
        is_pending,
        can_post,
        can_read,
        can_manage_posts,
        can_manage_community,
        can_manage_roles,
        can_manage_questions,
    );

    // return
    Ok(Html(
        data.1
            .render("communities/question.html", &context)
            .unwrap(),
    ))
}