use anyhow::bail;
use anyhow::Result;
use std::sync::Arc;

use super::implementations::create_plugin;
use crate::environment::Environment;
use crate::plugins::Plugin;
use crate::plugins::PluginCache;
use crate::plugins::PluginSourceReference;
use crate::plugins::PluginsCollection;

#[derive(Clone)]
pub struct PluginResolver<TEnvironment: Environment> {
  environment: TEnvironment,
  plugin_cache: Arc<PluginCache<TEnvironment>>,
  plugins_collection: Arc<PluginsCollection<TEnvironment>>,
}

impl<TEnvironment: Environment> PluginResolver<TEnvironment> {
  pub fn new(environment: TEnvironment, plugin_cache: Arc<PluginCache<TEnvironment>>, plugins_collection: Arc<PluginsCollection<TEnvironment>>) -> Self {
    PluginResolver {
      environment,
      plugin_cache,
      plugins_collection,
    }
  }

  pub async fn resolve_plugins(&self, plugin_references: Vec<PluginSourceReference>) -> Result<Vec<Box<dyn Plugin>>> {
    let handles = plugin_references
      .into_iter()
      .map(|plugin_ref| {
        let resolver = self.clone();
        tokio::task::spawn(async move { resolver.resolve_plugin(&plugin_ref).await })
      })
      .collect::<Vec<_>>();

    let results = futures::future::join_all(handles).await;
    let mut plugins = Vec::with_capacity(results.len());
    for result in results {
      plugins.push(result??);
    }

    Ok(plugins)
  }

  pub async fn resolve_plugin(&self, plugin_reference: &PluginSourceReference) -> Result<Box<dyn Plugin>> {
    match create_plugin(self.plugins_collection.clone(), &self.plugin_cache, self.environment.clone(), plugin_reference).await {
      Ok(plugin) => Ok(plugin),
      Err(err) => {
        match self.plugin_cache.forget(plugin_reference) {
          Ok(()) => {}
          Err(inner_err) => {
            bail!(
              "Error resolving plugin {} and forgetting from cache: {:#}\n{:#}",
              plugin_reference.display(),
              err,
              inner_err
            )
          }
        }
        bail!("Error resolving plugin {}: {:#}", plugin_reference.display(), err);
      }
    }
  }
}
