use magnetar_model::ck;
use magnetar_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
use magnetar_sdk::types::drive::PackDriveFileBase;
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
use magnetar_sdk::types::instance::InstanceTicker;
use magnetar_sdk::types::note::PackNoteMaybeFull;
use magnetar_sdk::types::notification::NotificationType;
use magnetar_sdk::types::user::{
    AvatarDecoration, MovedTo, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
    UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx,
    UserRelationExt, UserSecretsExt, UserSelfExt,
};
use magnetar_sdk::types::{MmXml, NotificationSettings};
use serde::Deserialize;
use url::Url;

use crate::model::{PackType, PackingContext};

impl PackType<&[PackEmojiBase]> for EmojiContext {
    fn extract(_context: &PackingContext, data: &[PackEmojiBase]) -> Self {
        EmojiContext(data.to_owned())
    }
}

pub struct UserBaseSource<'a> {
    pub user: &'a ck::user::Model,
    pub username_mm: Option<&'a MmXml>,
    pub avatar_url: &'a Url,
    pub avatar: Option<&'a PackDriveFileBase>,
    pub emoji_context: &'a EmojiContext,
    pub instance: Option<&'a InstanceTicker>,
}

impl PackType<UserBaseSource<'_>> for UserBase {
    fn extract(
        _context: &PackingContext,
        UserBaseSource {
            user,
            username_mm,
            avatar_url,
            avatar,
            emoji_context,
            instance,
        }: UserBaseSource,
    ) -> Self {
        UserBase {
            acct: user
                .host
                .as_ref()
                .map(|host| format!("@{}@{}", user.username, host))
                .unwrap_or_else(|| format!("@{}", user.username)),
            username: user.username.clone(),
            display_name: user.name.clone().unwrap_or_else(|| user.username.clone()),
            display_name_mm: username_mm.cloned(),
            host: user.host.clone(),
            speech_transform: if user.is_cat && user.speak_as_cat {
                SpeechTransform::Cat
            } else {
                SpeechTransform::None
            },
            created_at: user.created_at.into(),
            avatar_url: avatar_url.to_string(),
            avatar_blurhash: avatar.and_then(|v| v.file.0.blurhash.clone()),
            avatar_decoration: if user.is_cat {
                AvatarDecoration::CatEars
            } else {
                AvatarDecoration::None
            },
            is_admin: user.is_admin,
            is_moderator: user.is_moderator,
            is_bot: user.is_bot,
            emojis: emoji_context.clone(),
            instance: instance.cloned(),
        }
    }
}

pub struct UserProfileExtSource<'a> {
    pub user: &'a ck::user::Model,
    pub profile: &'a ck::user_profile::Model,
    pub profile_fields: &'a Vec<ProfileField>,
    pub banner: Option<&'a PackDriveFileBase>,
    pub description_mm: Option<&'a MmXml>,
    pub relation: Option<&'a UserRelationExt>,
    pub moved_to: Option<&'a MovedTo>,
}

impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
    fn extract(
        context: &PackingContext,
        UserProfileExtSource {
            user,
            profile,
            profile_fields,
            banner,
            description_mm,
            relation,
            moved_to,
        }: UserProfileExtSource,
    ) -> Self {
        let follow_visibility = match profile.ff_visibility {
            UserProfileFfvisibilityEnum::Public => true,
            UserProfileFfvisibilityEnum::Followers => {
                relation.is_some_and(|r| r.follows_you) || context.is_self(user)
            }
            UserProfileFfvisibilityEnum::Private => false,
        } || context.is_self(user);

        UserProfileExt {
            is_locked: user.is_locked,
            is_silenced: user.is_silenced,
            is_suspended: user.is_suspended,
            description: profile.description.clone(),
            description_mm: description_mm.cloned(),
            location: profile.location.clone(),
            birthday: profile.birthday.clone().and_then(|b| b.parse().ok()),
            fields: profile_fields.clone(),
            follower_count: follow_visibility.then_some(user.followers_count as usize),
            following_count: follow_visibility.then_some(user.following_count as usize),
            note_count: Some(user.notes_count as usize),
            url: profile.url.clone(),
            moved_to: moved_to.cloned(),
            also_known_as: user.also_known_as.clone(),
            banner_url: banner.and_then(|b| b.file.0.url.to_owned()),
            banner_blurhash: banner.and_then(|b| b.file.0.blurhash.clone()),
            has_public_reactions: profile.public_reactions,
        }
    }
}

impl PackType<&ck::user::Model> for UserDetailExt {
    fn extract(_context: &PackingContext, user: &ck::user::Model) -> Self {
        UserDetailExt {
            last_fetched_at: user.last_fetched_at.map(Into::into),
            uri: user.uri.clone(),
            updated_at: user.updated_at.map(Into::into),
        }
    }
}

pub struct UserRelationExtSource {
    pub follow_out: bool,
    pub follow_in: bool,
    pub follow_request_in: bool,
    pub follow_request_out: bool,
    pub block_out: bool,
    pub block_in: bool,
    pub mute: bool,
    pub renote_mute: bool,
}

impl PackType<UserRelationExtSource> for UserRelationExt {
    fn extract(
        _context: &PackingContext,
        UserRelationExtSource {
            follow_out,
            follow_in,
            follow_request_in,
            follow_request_out,
            block_out,
            block_in,
            mute,
            renote_mute,
        }: UserRelationExtSource,
    ) -> Self {
        UserRelationExt {
            follows_you: follow_in,
            you_follow: follow_out,
            they_request_follow: follow_request_in,
            you_request_follow: follow_request_out,
            blocks_you: block_in,
            you_block: block_out,
            mute,
            mute_renotes: renote_mute,
        }
    }
}

impl PackType<&[PackNoteMaybeFull]> for UserProfilePinsEx {
    fn extract(_context: &PackingContext, pinned_notes: &[PackNoteMaybeFull]) -> Self {
        UserProfilePinsEx {
            pinned_notes: pinned_notes.to_owned(),
        }
    }
}

impl PackType<ck::user_security_key::Model> for SecurityKeyBase {
    fn extract(_context: &PackingContext, key: ck::user_security_key::Model) -> Self {
        SecurityKeyBase {
            name: key.name.clone(),
            last_used_at: Some(key.last_used.into()),
        }
    }
}

type UserSecretsExtSource<'a> = (&'a ck::user_profile::Model, &'a [PackSecurityKeyBase]);

impl PackType<UserSecretsExtSource<'_>> for UserSecretsExt {
    fn extract(_context: &PackingContext, (profile, keys): UserSecretsExtSource) -> Self {
        UserSecretsExt {
            email: profile.email.clone(),
            email_verified: profile.email_verified,
            security_keys: keys.to_owned(),
        }
    }
}

impl PackType<&ck::user_profile::Model> for UserAuthOverviewExt {
    fn extract(_context: &PackingContext, data: &ck::user_profile::Model) -> Self {
        UserAuthOverviewExt {
            has_passwordless_login: data.use_password_less_login,
            has_security_keys: data.security_keys_available,
            has_two_factor_enabled: data.two_factor_enabled,
        }
    }
}

impl PackType<(&ck::user::Model, &ck::user_profile::Model)> for UserSelfExt {
    fn extract(
        context: &PackingContext,
        (user, profile): (&ck::user::Model, &ck::user_profile::Model),
    ) -> Self {
        UserSelfExt {
            avatar_id: user.avatar_id.clone(),
            banner_id: user.banner_id.clone(),
            email_announcements: profile.receive_announcement_email,
            always_mark_sensitive: profile.always_mark_nsfw,
            reject_bot_follow_requests: profile.careful_bot,
            reject_crawlers: profile.no_crawle,
            reject_ai_training: profile.prevent_ai_learning,
            word_mutes: Vec::<String>::deserialize(&profile.muted_words).unwrap_or_else(|_| vec![]),
            instance_mutes: vec![],
            notification_settings: NotificationSettings {
                enabled: profile
                    .muting_notification_types
                    .iter()
                    .map(|n| NotificationType::extract(context, n))
                    .collect::<Vec<_>>(),
            },
        }
    }
}
