use crate::model::processing::{PackError, PackResult};
use crate::model::{PackType, PackingContext};
use either::Either;
use futures_util::TryFutureExt;
use itertools::Itertools;
use magnetar_common::util::{parse_reaction, RawReaction};
use magnetar_model::ck;
use magnetar_model::emoji::EmojiTag;
use magnetar_sdk::types::emoji::{EmojiBase, PackEmojiBase};
use magnetar_sdk::types::note::{Reaction, ReactionShortcode, ReactionUnicode, ReactionUnknown};
use magnetar_sdk::types::Id;
use magnetar_sdk::{Packed, Required};
use std::sync::Arc;

pub fn parse_emoji_or_raw(tag: &str) -> Either<String, RawReaction> {
    parse_reaction(tag).map_or_else(|| Either::Left(tag.to_string()), Either::Right)
}

pub fn shortcode_tag_or_none(value: &RawReaction) -> Option<EmojiTag<'_>> {
    match value {
        RawReaction::Shortcode { shortcode, host } => Some(EmojiTag {
            name: shortcode.as_str(),
            host: host.as_deref(),
        }),
        _ => None,
    }
}

pub fn resolve_reaction<'a>(
    value: RawReaction,
    code_lookup: &impl Fn(&str, Option<&str>) -> Option<&'a ck::emoji::Model>,
) -> Reaction {
    match value {
        RawReaction::Unicode(text) => Reaction::Unicode(ReactionUnicode(text)),
        RawReaction::Shortcode { shortcode, host } => code_lookup(&shortcode, host.as_deref())
            .map_or_else(
                || {
                    Reaction::Unknown(ReactionUnknown {
                        raw: format!(
                            ":{shortcode}{}:",
                            host.as_deref().map(|h| format!("@{h}")).unwrap_or_default()
                        ),
                    })
                },
                |e| {
                    Reaction::Shortcode(ReactionShortcode {
                        name: shortcode.clone(),
                        host: host.clone(),
                        url: e.public_url.clone(),
                    })
                },
            ),
    }
}

pub struct EmojiModel;

impl EmojiModel {
    pub fn pack_existing(&self, ctx: &PackingContext, emoji: &ck::emoji::Model) -> PackEmojiBase {
        PackEmojiBase::pack_from((
            Required(Id::from(&emoji.id)),
            Required(EmojiBase::extract(ctx, emoji)),
        ))
    }

    pub async fn fetch_many_emojis(
        &self,
        ctx: &PackingContext,
        shortcodes: &[String],
        host: Option<&str>,
    ) -> PackResult<Vec<PackEmojiBase>> {
        let emojis = ctx.service.emoji_cache.get_many(shortcodes, host).await?;
        let packed_emojis = emojis.iter().map(|e| self.pack_existing(ctx, e)).collect();

        Ok(packed_emojis)
    }

    pub async fn fetch_many_tag_emojis(
        &self,
        ctx: &PackingContext,
        tags: &[EmojiTag<'_>],
    ) -> PackResult<Vec<PackEmojiBase>> {
        let emojis = ctx.service.emoji_cache.get_many_tagged(tags).await?;
        let packed_emojis = emojis.iter().map(|e| self.pack_existing(ctx, e)).collect();

        Ok(packed_emojis)
    }

    pub fn deduplicate_emoji(&self, ctx: &PackingContext, emoji_list: Vec<String>) -> Vec<String> {
        emoji_list
            .into_iter()
            .sorted()
            .dedup()
            .take(ctx.limits.max_emojis)
            .collect::<Vec<_>>()
    }

    pub async fn resolve_reaction(
        &self,
        ctx: &PackingContext,
        reaction: &str,
    ) -> PackResult<Reaction> {
        let parsed = parse_emoji_or_raw(reaction);

        Ok(match parsed {
            Either::Left(raw) => Reaction::Unknown(ReactionUnknown { raw }),
            Either::Right(raw) => {
                let reaction_fetched = match shortcode_tag_or_none(&raw) {
                    Some(tag) => {
                        ctx.service
                            .emoji_cache
                            .get(tag.name, tag.host)
                            .map_err(PackError::from)
                            .await?
                    }
                    None => None,
                };

                let reaction_ref = reaction_fetched.as_ref().map(Arc::as_ref);
                resolve_reaction(raw, &move |_, _| reaction_ref)
            }
        })
    }

    pub async fn resolve_reactions_many(
        &self,
        ctx: &PackingContext,
        reactions_raw: &[String],
    ) -> PackResult<Vec<Reaction>> {
        let reactions_parsed = reactions_raw
            .iter()
            .map(String::as_ref)
            .map(parse_emoji_or_raw)
            .collect::<Vec<_>>();

        // Pick out all successfully-parsed shortcode emojis
        let reactions_to_resolve = reactions_parsed
            .iter()
            .map(Either::as_ref)
            .filter_map(Either::right)
            .filter_map(shortcode_tag_or_none)
            .collect::<Vec<_>>();

        let reactions_fetched = ctx
            .service
            .emoji_cache
            .get_many_tagged(&reactions_to_resolve)
            .map_err(PackError::from)
            .await?;

        // Left reactions and the Right ones that didn't resolve to any emoji are turned back into Unknown
        let reactions_resolved = reactions_parsed
            .into_iter()
            .map(|val| {
                val.either(
                    |raw| Reaction::Unknown(ReactionUnknown { raw }),
                    |raw| {
                        resolve_reaction(raw, &|shortcode, host| {
                            reactions_fetched
                                .iter()
                                .find(|e| e.host.as_deref() == host && e.name == shortcode)
                                .map(Arc::as_ref)
                        })
                    },
                )
            })
            .collect::<Vec<_>>();

        Ok(reactions_resolved)
    }
}
