use super::*;

#[cfg(feature = "entity-manager")]
impl<Related: Model, Pivot: Model> HasManyThrough<Related, Pivot> {
    pub fn with_metadata(
        mut self,
        relation_name: &'static str,
        owner_table: &'static str,
        related_table: &'static str,
    ) -> Self {
        self.relation_name = relation_name;
        self.owner_table = owner_table;
        self.related_table = related_table;
        self
    }

    pub fn with_owner_key(mut self, owner_key: String) -> Self {
        self.owner_key = Some(owner_key);
        self
    }

    #[doc(hidden)]
    pub fn attach_query_database(&mut self, database: &crate::database::Database) {
        self.query_db = Some(database.clone());
    }

    pub(super) fn scoped_database(&self) -> Result<crate::database::Database> {
        if matches!(
            crate::database::__current_connection(),
            Ok(crate::database::ConnectionRef::Transaction(_))
        ) {
            return crate::database::__current_db();
        }

        if let Some(entity_manager) = &self.entity_manager {
            return Ok(entity_manager.database().clone());
        }

        if let Some(db) = &self.query_db {
            return Ok(db.clone());
        }

        crate::database::require_db()
    }

    fn scoped_database_for_entity_manager(
        entity_manager: &std::sync::Arc<crate::entity_manager::EntityManager>,
    ) -> Result<crate::database::Database> {
        if matches!(
            crate::database::__current_connection(),
            Ok(crate::database::ConnectionRef::Transaction(_))
        ) {
            return crate::database::__current_db();
        }

        Ok(entity_manager.database().clone())
    }

    pub fn current_keys(&self) -> Result<Vec<String>>
    where
        Related: crate::model::ModelMeta,
    {
        let mut keys = Vec::new();
        for item in self.cached.as_deref().unwrap_or(&[]) {
            if let Some(key) = crate::entity_manager::__model_entity_manager_key(item)? {
                keys.push(key);
            }
        }

        Ok(keys)
    }

    pub async fn load_in_entity_manager(
        &mut self,
        entity_manager: &std::sync::Arc<crate::entity_manager::EntityManager>,
    ) -> Result<&Vec<Related>>
    where
        Related: crate::internal::InternalModel
            + crate::entity_manager::TideEntityManagerMeta
            + Clone
            + Send
            + Sync
            + 'static,
    {
        if let Some(items) = self.cached.as_ref() {
            for entity in items.iter().cloned() {
                entity_manager.put(entity);
            }

            self.entity_manager = Some(entity_manager.clone());
            let owner_key = self.owner_key.as_deref().ok_or_else(|| {
                Error::query(format!(
                    "entity manager owner key not set for relation '{}'",
                    self.relation_name
                ))
            })?;
            let ids = self.current_keys()?;
            entity_manager
                .snapshot::<Related>(self.owner_table, owner_key, self.relation_name, &ids)
                .await;
            return Ok(self.cached.as_ref().expect("relation cache should exist"));
        }

        self.ensure_configured()?;

        let pk = self
            .parent_pk
            .as_ref()
            .ok_or_else(|| Error::query(String::from("Parent primary key not set for relation")))?;
        let pk = require_scalar_relation_key(pk, "HasManyThrough::load_in_entity_manager")?;
        let owner_key = self.owner_key.as_deref().ok_or_else(|| {
            Error::query(format!(
                "entity manager owner key not set for relation '{}'",
                self.relation_name
            ))
        })?;
        let pivot_related_column = format!("{}.{}", self.pivot_table, self.related_key);
        let related_local_column = format!("{}.{}", Related::table_name(), self.related_local_key);

        let db = Self::scoped_database_for_entity_manager(entity_manager)?;
        let loaded = Related::query_with(&db)
            .inner_join(
                self.pivot_table,
                &pivot_related_column,
                &related_local_column,
            )
            .where_eq(
                format!("{}.{}", self.pivot_table, self.foreign_key),
                pk.clone(),
            )
            .get()
            .await?;

        let mut registered = Vec::with_capacity(loaded.len());
        for entity in loaded {
            registered.push(entity_manager.register(entity).await);
        }

        self.cached = Some(registered);
        self.entity_manager = Some(entity_manager.clone());

        let ids = self.current_keys()?;
        entity_manager
            .snapshot::<Related>(self.owner_table, owner_key, self.relation_name, &ids)
            .await;

        Ok(self.cached.as_ref().expect("relation cache should exist"))
    }
}

#[cfg(feature = "entity-manager")]
impl<Related, Pivot> crate::entity_manager::EntityManagerLoad for HasManyThrough<Related, Pivot>
where
    Related: crate::internal::InternalModel
        + crate::entity_manager::TideEntityManagerMeta
        + Model
        + Clone
        + Send
        + Sync
        + 'static,
    Pivot: Model,
{
    type Output<'a>
        = &'a Vec<Related>
    where
        Self: 'a;

    fn load_with_entity_manager<'a>(
        &'a mut self,
        entity_manager: &'a std::sync::Arc<crate::entity_manager::EntityManager>,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self::Output<'a>>> + Send + 'a>>
    {
        Box::pin(async move { self.load_in_entity_manager(entity_manager).await })
    }
}
