use crate::model::data::id::BaseId;
use crate::model::processing::emoji::EmojiModel;
use crate::model::processing::note::{NoteModel, NoteVisibilityFilterModel};
use crate::model::processing::user::UserModel;
use crate::model::processing::{PackError, PackResult};
use crate::model::{PackType, PackingContext};
use crate::web::pagination::Pagination;
use futures_util::{StreamExt, TryStreamExt};
use magnetar_model::ck;
use magnetar_model::ck::sea_orm_active_enums::NotificationTypeEnum;
use magnetar_model::note_model::NoteResolveOptions;
use magnetar_model::notification_model::{NotificationData, NotificationResolveOptions};
use magnetar_model::user_model::UserResolveOptions;
use magnetar_sdk::types::notification::{
    NotificationAppExt, NotificationBase, NotificationNoteExt, NotificationReactionExt,
    NotificationType, NotificationUserExt, PackNotification, PackNotificationApp,
    PackNotificationFollow, PackNotificationFollowRequestAccepted,
    PackNotificationFollowRequestReceived, PackNotificationMention, PackNotificationPollEnd,
    PackNotificationQuote, PackNotificationReaction, PackNotificationRenote, PackNotificationReply,
};
use magnetar_sdk::types::Id;
use magnetar_sdk::{Packed, Required};
use std::sync::Arc;

impl PackType<&NotificationType> for NotificationTypeEnum {
    fn extract(_: &PackingContext, value: &NotificationType) -> Self {
        use NotificationType as NT;
        use NotificationTypeEnum as NTE;
        match value {
            NT::Follow => NTE::Follow,
            NT::FollowRequestReceived => NTE::ReceiveFollowRequest,
            NT::FollowRequestAccepted => NTE::FollowRequestAccepted,
            NT::Mention => NTE::Mention,
            NT::Reply => NTE::Reply,
            NT::Renote => NTE::Renote,
            NT::Reaction => NTE::Reaction,
            NT::Quote => NTE::Quote,
            NT::PollEnd => NTE::PollEnded,
            NT::App => NTE::App,
        }
    }
}

impl PackType<&NotificationTypeEnum> for NotificationType {
    fn extract(_: &PackingContext, value: &NotificationTypeEnum) -> Self {
        use NotificationType as NT;
        use NotificationTypeEnum as NTE;
        match value {
            NTE::Follow => NT::Follow,
            NTE::ReceiveFollowRequest => NT::FollowRequestReceived,
            NTE::FollowRequestAccepted => NT::FollowRequestAccepted,
            NTE::Mention => NT::Mention,
            NTE::Reply => NT::Reply,
            NTE::Renote => NT::Renote,
            NTE::Reaction => NT::Reaction,
            NTE::Quote => NT::Quote,
            NTE::PollEnded => NT::PollEnd,
            NTE::App => NT::App,
        }
    }
}

pub struct NotificationModel;

impl NotificationModel {
    async fn pack_notification_single(
        &self,
        ctx: &PackingContext,
        notification_data: &NotificationData,
        note_model: &NoteModel,
        user_model: &UserModel,
        emoji_model: &EmojiModel,
    ) -> PackResult<PackNotification> {
        let notification_type =
            NotificationType::extract(ctx, &notification_data.notification.r#type);
        let id = Required(Id::from(&notification_data.notification.id));
        let base = Required(NotificationBase::extract(
            ctx,
            &notification_data.notification,
        ));

        let notifier = notification_data
            .notifier
            .as_ref()
            .ok_or_else(|| PackError::DataError("Missing notification user".to_string()));
        let note = notification_data
            .notification_note
            .as_ref()
            .ok_or_else(|| PackError::DataError("Missing notification note".to_string()));
        let access_token = notification_data
            .access_token
            .as_ref()
            .ok_or_else(|| PackError::DataError("Missing notification access token".to_string()));

        Ok(match notification_type {
            NotificationType::Follow => {
                PackNotification::Follow(PackNotificationFollow::pack_from((
                    id,
                    base,
                    Required(NotificationUserExt::extract(
                        ctx,
                        &user_model.base_from_existing(ctx, &notifier?).await?,
                    )),
                )))
            }
            NotificationType::FollowRequestReceived => PackNotification::FollowRequestReceived(
                PackNotificationFollowRequestReceived::pack_from((
                    id,
                    base,
                    Required(NotificationUserExt::extract(
                        ctx,
                        &user_model.base_from_existing(ctx, &notifier?).await?,
                    )),
                )),
            ),
            NotificationType::FollowRequestAccepted => PackNotification::FollowRequestAccepted(
                PackNotificationFollowRequestAccepted::pack_from((
                    id,
                    base,
                    Required(NotificationUserExt::extract(
                        ctx,
                        &user_model.base_from_existing(ctx, &notifier?).await?,
                    )),
                )),
            ),
            NotificationType::Mention => {
                PackNotification::Mention(PackNotificationMention::pack_from((
                    id,
                    base,
                    Required(NotificationNoteExt::extract(
                        ctx,
                        &note_model.pack_full_single(ctx, &note?).await?,
                    )),
                )))
            }
            NotificationType::Reply => PackNotification::Reply(PackNotificationReply::pack_from((
                id,
                base,
                Required(NotificationNoteExt::extract(
                    ctx,
                    &note_model.pack_full_single(ctx, &note?).await?,
                )),
            ))),
            NotificationType::Renote => {
                PackNotification::Renote(PackNotificationRenote::pack_from((
                    id,
                    base,
                    Required(NotificationNoteExt::extract(
                        ctx,
                        &note_model.pack_full_single(ctx, &note?).await?,
                    )),
                )))
            }
            NotificationType::Reaction => {
                PackNotification::Reaction(PackNotificationReaction::pack_from((
                    id,
                    base,
                    Required(NotificationNoteExt::extract(
                        ctx,
                        &note_model.pack_full_single(ctx, &note?).await?,
                    )),
                    Required(NotificationReactionExt::extract(
                        ctx,
                        &emoji_model
                            .resolve_reaction(
                                ctx,
                                notification_data
                                    .notification
                                    .reaction
                                    .as_deref()
                                    .ok_or_else(|| {
                                        PackError::DataError(
                                            "Missing notification reaction".to_string(),
                                        )
                                    })?,
                            )
                            .await?,
                    )),
                    Required(NotificationUserExt::extract(
                        ctx,
                        &user_model.base_from_existing(ctx, &notifier?).await?,
                    )),
                )))
            }
            NotificationType::Quote => PackNotification::Quote(PackNotificationQuote::pack_from((
                id,
                base,
                Required(NotificationNoteExt::extract(
                    ctx,
                    &note_model.pack_full_single(ctx, &note?).await?,
                )),
            ))),
            NotificationType::PollEnd => {
                PackNotification::PollEnd(PackNotificationPollEnd::pack_from((
                    id,
                    base,
                    Required(NotificationNoteExt::extract(
                        ctx,
                        &note_model.pack_full_single(ctx, &note?).await?,
                    )),
                )))
            }
            NotificationType::App => PackNotification::App(PackNotificationApp::pack_from((
                id,
                base,
                Required(NotificationAppExt::extract(
                    ctx,
                    (&notification_data.notification, access_token?),
                )),
            ))),
        })
    }

    pub async fn get_notification(
        &self,
        ctx: &PackingContext,
        notification_id: &str,
        user_id: &str,
    ) -> PackResult<Option<PackNotification>> {
        let user_resolve_options = UserResolveOptions {
            with_avatar: true,
            with_banner: false,
            with_profile: false,
        };

        let self_id = ctx.self_user.as_deref().map(ck::user::Model::get_id);
        let Some(notification_raw) = ctx
            .service
            .db
            .get_notification_resolver()
            .get_single(
                &NotificationResolveOptions {
                    note_options: NoteResolveOptions {
                        ids: None,
                        visibility_filter: Arc::new(
                            NoteVisibilityFilterModel.new_note_visibility_filter(Some(user_id)),
                        ),
                        time_range: None,
                        limit: None,
                        with_reply_target: true,
                        with_renote_target: true,
                        with_interactions_from: self_id.map(str::to_string),
                        only_pins_from: None,
                        user_options: user_resolve_options.clone(),
                    },
                    user_options: user_resolve_options,
                },
                notification_id,
            )
            .await?
        else {
            return Ok(None);
        };

        let note_model = NoteModel {
            with_context: true,
            attachments: false,
        };
        let user_model = UserModel;
        let emoji_model = EmojiModel;

        let notification = self
            .pack_notification_single(
                ctx,
                &notification_raw,
                &note_model,
                &user_model,
                &emoji_model,
            )
            .await?;

        Ok(Some(notification))
    }

    pub async fn get_notifications(
        &self,
        ctx: &PackingContext,
        id: &str,
        notification_types: &[NotificationType],
        unread_only: bool,
        pagination: &mut Pagination,
    ) -> PackResult<Vec<PackNotification>> {
        let user_resolve_options = UserResolveOptions {
            with_avatar: true,
            with_banner: false,
            with_profile: false,
        };

        let self_id = ctx.self_user.as_deref().map(ck::user::Model::get_id);
        let notifications_raw = ctx
            .service
            .db
            .get_notification_resolver()
            .get(
                &NotificationResolveOptions {
                    note_options: NoteResolveOptions {
                        ids: None,
                        visibility_filter: Arc::new(
                            NoteVisibilityFilterModel.new_note_visibility_filter(Some(id)),
                        ),
                        time_range: None,
                        limit: None,
                        with_reply_target: true,
                        with_renote_target: true,
                        with_interactions_from: self_id.map(str::to_string),
                        only_pins_from: None,
                        user_options: user_resolve_options.clone(),
                    },
                    user_options: user_resolve_options,
                },
                id,
                &notification_types
                    .iter()
                    .map(|v| NotificationTypeEnum::extract(ctx, v))
                    .collect::<Vec<_>>(),
                unread_only,
                &pagination.current,
                &mut pagination.prev,
                &mut pagination.next,
                pagination.limit.into(),
            )
            .await?;

        let note_model = NoteModel {
            with_context: true,
            attachments: false,
        };
        let user_model = UserModel;
        let emoji_model = EmojiModel;

        let fut_iter = notifications_raw
            .iter()
            .map(|n| self.pack_notification_single(ctx, n, &note_model, &user_model, &emoji_model))
            .collect::<Vec<_>>();

        let processed = futures::stream::iter(fut_iter)
            .buffered(10)
            .err_into::<PackError>()
            .try_collect::<Vec<_>>()
            .await?;

        Ok(processed)
    }
}
