use crate::model::data::id::BaseId;
use crate::model::processing::PackResult;
use crate::service::MagnetarService;
use either::Either;
use magnetar_model::{ck, CalckeyDbError};
use magnetar_sdk::types::user::UserRelationship;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

pub mod data;
pub mod processing;

#[derive(Clone, Debug)]
pub struct ProcessingLimits {
    max_emojis: usize,
}

impl Default for ProcessingLimits {
    fn default() -> Self {
        ProcessingLimits { max_emojis: 500 }
    }
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct UserRelationshipLink {
    from: String,
    to: String,
    rel_type: UserRelationship,
}

#[derive(Clone, Debug)]
pub struct PackingContext {
    instance_meta: Arc<ck::meta::Model>,
    self_user: Option<Arc<ck::user::Model>>,
    service: Arc<MagnetarService>,
    limits: ProcessingLimits,
    relationships: Arc<Mutex<HashMap<UserRelationshipLink, bool>>>,
}

pub trait PackType<I>: 'static {
    fn extract(context: &PackingContext, data: I) -> Self;
}

impl PackingContext {
    pub async fn new(
        service: Arc<MagnetarService>,
        self_user: Option<Arc<ck::user::Model>>,
    ) -> PackResult<PackingContext> {
        Ok(Self {
            instance_meta: service.instance_meta_cache.get().await?,
            self_user,
            service,
            limits: Default::default(),
            relationships: Arc::new(Mutex::new(HashMap::new())),
        })
    }

    fn self_user(&self) -> Option<&ck::user::Model> {
        self.self_user.as_deref()
    }

    fn is_id_self(&self, user_id: &str) -> bool {
        self.self_user()
            .is_some_and(|self_user| self_user.id == user_id)
    }

    fn is_self(&self, user: &ck::user::Model) -> bool {
        self.is_id_self(&user.id)
    }

    async fn has_relationship_with(
        &self,
        user: &ck::user::Model,
        rel_type: UserRelationship,
    ) -> Result<bool, CalckeyDbError> {
        let Some(self_user) = self.self_user.as_deref() else {
            return Ok(false);
        };

        self.is_relationship_between(Either::Right(self_user), Either::Right(user), rel_type)
            .await
    }

    async fn is_relationship_between(
        &self,
        from: Either<&str, &ck::user::Model>,
        to: Either<&str, &ck::user::Model>,
        rel_type: UserRelationship,
    ) -> Result<bool, CalckeyDbError> {
        let link = UserRelationshipLink {
            from: from.left_or_else(ck::user::Model::get_id).to_string(),
            to: to.left_or_else(ck::user::Model::get_id).to_string(),
            rel_type,
        };

        let read = self.relationships.lock().await;
        if let Some(relationship) = read.get(&link) {
            return Ok(*relationship);
        }
        drop(read);

        let relationship = match rel_type {
            UserRelationship::Block => self
                .service
                .db
                .get_block_status(&link.from, &link.to)
                .await?
                .is_some(),
            UserRelationship::Follow => self
                .service
                .db
                .get_follower_status(&link.from, &link.to)
                .await?
                .is_some(),
            UserRelationship::FollowRequest => self
                .service
                .db
                .get_follow_request_status(&link.from, &link.to)
                .await?
                .is_some(),
            UserRelationship::Mute => self
                .service
                .db
                .get_mute_status(&link.from, &link.to)
                .await?
                .is_some(),
            UserRelationship::RenoteMute => self
                .service
                .db
                .get_renote_mute_status(&link.from, &link.to)
                .await?
                .is_some(),
        };

        let mut write = self.relationships.lock().await;
        write.insert(link, relationship);
        drop(write);

        Ok(relationship)
    }
}
