use crate::{
    check_user_is_private_and_following,
    database::DataManager,
    model::{
        Result,
        auth::User,
        id::Id,
        permissions::FinePermission,
        posts::{Poll, Post, Question},
    },
};
use std::collections::HashMap;

pub type PostQuestion = (Question, User, Option<(Post, User)>);

pub async fn get_post_poll(
    m: &DataManager,
    post: &Post,
    as_user: &Option<&User>,
    cache_map: &mut HashMap<String, (Poll, bool)>,
) -> Result<Option<(Poll, bool)>> {
    let poll_id = match post.poll_id {
        Some(ref x) => x,
        None => return Ok(None),
    };

    if poll_id == &Id::default() {
        return Ok(None);
    }

    if let Some((poll, has_voted)) = cache_map.get(&poll_id.printable()) {
        Ok(Some((poll.to_owned(), has_voted.to_owned())))
    } else {
        let poll = m.get_poll_by_id(poll_id).await?;
        let has_voted = if let Some(ua) = as_user {
            m.get_poll_vote_by_owner_poll(&ua.id, &poll.id)
                .await
                .is_ok()
        } else {
            false
        };

        cache_map.insert(poll_id.printable(), (poll.clone(), has_voted));
        Ok(Some((poll, has_voted)))
    }
}

pub async fn get_post_answering(
    m: &DataManager,
    post: &Post,
    as_user: &Option<&User>,
    cache_map: &mut HashMap<String, PostQuestion>,
    aa_cache_map: &mut HashMap<String, (Post, User)>,
) -> Result<Option<PostQuestion>> {
    let answering = match post.context.answering {
        Some(ref x) => x,
        None => return Ok(None),
    };

    if let Some((question, owner, asking_about)) = cache_map.get(&answering.printable()) {
        Ok(Some((
            question.clone(),
            owner.clone(),
            asking_about.clone(),
        )))
    } else {
        let question = match m.get_question_by_id(answering).await {
            Ok(x) => x,
            Err(_) => return Ok(None),
        };

        let owner = if question.owner == Id::Legacy(0)
            || question.context.mask_owner
                && (as_user.is_none()
                    || !as_user
                        .as_ref()
                        .unwrap()
                        .permissions
                        .check(FinePermission::ManageUsers))
        {
            User::anonymous()
        } else {
            m.get_user_by_id(&question.owner)
                .await
                .unwrap_or(User::deleted())
        };

        let asking_about = get_question_asking_about(m, &question, aa_cache_map).await?;

        cache_map.insert(
            answering.printable(),
            (question.clone(), owner.clone(), asking_about.clone()),
        );

        Ok(Some((question.clone(), owner, asking_about)))
    }
}

pub async fn get_question_asking_about(
    m: &DataManager,
    question: &Question,
    cache_map: &mut HashMap<String, (Post, User)>,
) -> Result<Option<(Post, User)>> {
    let asking_about = match question.context.asking_about {
        Some(ref x) => x,
        None => return Ok(None),
    };

    if let Some((post, owner)) = cache_map.get(&asking_about.printable()) {
        Ok(Some((post.clone(), owner.clone())))
    } else {
        let post = match m.get_post_by_id(asking_about).await {
            Ok(x) => x,
            Err(_) => return Ok(None),
        };

        let owner = m
            .get_user_by_id(&post.owner)
            .await
            .unwrap_or(User::deleted());

        cache_map.insert(asking_about.printable(), (post.clone(), owner.clone()));
        Ok(Some((post.clone(), owner)))
    }
}

pub async fn get_post_reposting(
    m: &DataManager,
    post: &Post,
    as_user: &Option<&User>,
    cache_map: &mut HashMap<String, (Post, User)>,
    seen_private_users: &mut Vec<Id>,
    seen_blocks: &mut Vec<Id>,
) -> Result<Option<(Post, User)>> {
    let repost_ctx = match post.context.repost {
        Some(ref x) => x,
        None => return Ok(None),
    };

    let reposting = repost_ctx.reposting.as_ref().unwrap();
    if let Some((post, owner)) = cache_map.get(&reposting.printable()) {
        Ok(Some((post.clone(), owner.clone())))
    } else {
        // let post = m.get_post_by_id(&reposting).await?;
        let post = match m.get_post_by_id(reposting).await {
            Ok(x) => x,
            Err(_) => return Ok(None),
        };

        let owner = m
            .get_user_by_id(&post.owner)
            .await
            .unwrap_or(User::deleted());

        // check permissions against owner
        if owner.id != Id::default() {
            if check_is_blocked_can_view_post(m, &owner, seen_private_users, as_user).await {
                return Ok(None);
            }

            if owner.settings.private_profile
                && check_private_is_followed_can_view_post(m, &owner, seen_blocks, as_user).await
            {
                return Ok(None);
            }
        }

        // ...
        cache_map.insert(reposting.printable(), (post.clone(), owner.clone()));
        Ok(Some((post.clone(), owner)))
    }
}

pub async fn check_is_blocked_can_view_post(
    m: &DataManager,
    owner: &User,
    cache_map: &mut Vec<Id>,
    as_user: &Option<&User>,
) -> bool {
    if as_user.is_none() {
        return false;
    }

    let r = m
        .get_user_block_by_users(&owner.id, &as_user.as_ref().unwrap().id)
        .await
        .is_ok();

    if r {
        cache_map.push(owner.id.to_owned());
    }

    r
}

pub async fn check_private_is_followed_can_view_post(
    m: &DataManager,
    owner: &User,
    cache_map: &mut Vec<Id>,
    as_user: &Option<&User>,
) -> bool {
    if as_user.is_none() {
        return true;
    }

    let r = !check_user_is_private_and_following!(m, *as_user, owner);

    if r {
        cache_map.push(owner.id.to_owned());
    }

    r
}

pub async fn check_has_liked_post(
    m: &DataManager,
    post: &Post,
    as_user: &Option<&User>,
) -> (bool, bool) {
    if as_user.is_none() {
        return (false, false);
    }

    if let Ok(x) = m
        .get_reaction_by_owner_asset(&as_user.as_ref().unwrap().id, &post.id)
        .await
    {
        (x.is_like, true)
    } else {
        (false, false)
    }
}

pub async fn check_has_liked_question(
    m: &DataManager,
    question: &Question,
    as_user: &Option<&User>,
) -> (bool, bool) {
    if as_user.is_none() {
        return (false, false);
    }

    if let Ok(x) = m
        .get_reaction_by_owner_asset(&as_user.as_ref().unwrap().id, &question.id)
        .await
    {
        (x.is_like, true)
    } else {
        (false, false)
    }
}

pub async fn complete_timeline(
    m: &DataManager,
    posts: Vec<Post>,
    as_user: &Option<&User>,
) -> Result<
    Vec<(
        Post,
        User,
        Option<(Post, User)>,
        Option<PostQuestion>,
        Option<(Poll, bool)>,
        (bool, bool),
    )>,
> {
    let mut out = Vec::new();

    let mut seen_users: HashMap<String, User> = HashMap::new();
    let mut seen_reposts: HashMap<String, (Post, User)> = HashMap::new();
    let mut seen_questions: HashMap<String, PostQuestion> = HashMap::new();
    let mut seen_polls: HashMap<String, (Poll, bool)> = HashMap::new();
    let mut seen_asking_about: HashMap<String, (Post, User)> = HashMap::new();
    let mut seen_private_users: Vec<Id> = Vec::new();
    let mut seen_blocks: Vec<Id> = Vec::new();

    for post in posts {
        if let Some(ua) = seen_users.get(&post.owner.printable()) {
            if check_is_blocked_can_view_post(m, ua, &mut seen_private_users, as_user).await {
                continue;
            }

            if ua.settings.private_profile
                && check_private_is_followed_can_view_post(m, ua, &mut seen_blocks, as_user).await
            {
                continue;
            }

            let r = get_post_reposting(
                m,
                &post,
                as_user,
                &mut seen_reposts,
                &mut seen_private_users,
                &mut seen_blocks,
            )
            .await?;
            let q = get_post_answering(
                m,
                &post,
                as_user,
                &mut seen_questions,
                &mut seen_asking_about,
            )
            .await?;
            let p = get_post_poll(m, &post, as_user, &mut seen_polls).await?;
            let has_liked = check_has_liked_post(m, &post, as_user).await;

            out.push((post, ua.to_owned(), r, q, p, has_liked))
        } else {
            let user = m.get_user_by_id(&post.owner).await?;
            seen_users.insert(user.id.printable(), user.clone());

            if check_is_blocked_can_view_post(m, &user, &mut seen_blocks, as_user).await {
                continue;
            }

            if user.settings.private_profile
                && check_private_is_followed_can_view_post(
                    m,
                    &user,
                    &mut seen_private_users,
                    as_user,
                )
                .await
            {
                continue;
            }

            let r = get_post_reposting(
                m,
                &post,
                as_user,
                &mut seen_reposts,
                &mut seen_private_users,
                &mut seen_blocks,
            )
            .await?;
            let q = get_post_answering(
                m,
                &post,
                as_user,
                &mut seen_questions,
                &mut seen_asking_about,
            )
            .await?;
            let p = get_post_poll(m, &post, as_user, &mut seen_polls).await?;
            let has_liked = check_has_liked_post(m, &post, as_user).await;

            out.push((post, user, r, q, p, has_liked))
        }
    }

    Ok(out)
}

const MAX_MENTIONS: usize = 10;

pub async fn parse_mentions(m: &DataManager, input: &mut String, as_user: Id) -> Result<Vec<Id>> {
    let mut out = Vec::new();
    let mut cache: HashMap<String, Id> = HashMap::new();

    let mut escape: bool = false;
    let mut in_username: bool = false;
    let mut buffer: String = String::new();

    for x in input.clone().chars() {
        if out.len() == MAX_MENTIONS {
            continue;
        }

        match x {
            '[' | '\\' | '/' => escape = true,
            '@' => {
                if !escape {
                    in_username = true;
                }
            }
            ' ' => {
                if !escape && in_username {
                    in_username = false;

                    if !cache.contains_key(&buffer) {
                        let user = m.get_user_by_username(&buffer).await?;

                        if m.get_user_block_by_users(&as_user, &user.id).await.is_err() {
                            out.push(user.id.to_owned());
                            cache.insert(buffer.clone(), user.id.to_owned());

                            let _ = std::mem::replace(
                                input,
                                input.replace(
                                    &format!("@{buffer}"),
                                    &format!("[@{}](/@{}?redirect=true)", user.username, user.id),
                                ),
                            );
                        }
                    }

                    buffer.clear();
                }
            }
            _ => {
                if in_username {
                    buffer.push(x);
                }
            }
        }
    }

    if in_username && !cache.contains_key(&buffer) {
        let user = m.get_user_by_username(&buffer).await?;

        if m.get_user_block_by_users(&as_user, &user.id).await.is_err() {
            out.push(user.id.to_owned());

            let _ = std::mem::replace(
                input,
                input.replace(
                    &format!("@{buffer}"),
                    &format!("[@{}](/api/v2/users/find/{})", user.username, user.id),
                ),
            );
        }
    }

    Ok(out)
}

pub fn map_user(user: &Option<User>) -> Option<&User> {
    if let Some(user) = user {
        Some(user)
    } else {
        None
    }
}
