#[cfg(test)]
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::sync::Arc;
use router_bridge::introspect::IntrospectionError;
use router_bridge::planner::Planner;
use crate::cache::storage::CacheStorage;
use crate::graphql::Response;
use crate::query_planner::QueryPlanResult;
const DEFAULT_INTROSPECTION_CACHE_CAPACITY: NonZeroUsize =
    unsafe { NonZeroUsize::new_unchecked(5) };
pub(crate) struct Introspection {
    cache: CacheStorage<String, Response>,
    planner: Arc<Planner<QueryPlanResult>>,
}
impl Introspection {
    pub(crate) async fn with_capacity(
        planner: Arc<Planner<QueryPlanResult>>,
        capacity: NonZeroUsize,
    ) -> Self {
        Self {
            cache: CacheStorage::new(capacity, None, "introspection").await,
            planner,
        }
    }
    pub(crate) async fn new(planner: Arc<Planner<QueryPlanResult>>) -> Self {
        Self::with_capacity(planner, DEFAULT_INTROSPECTION_CACHE_CAPACITY).await
    }
    #[cfg(test)]
    pub(crate) async fn from_cache(
        planner: Arc<Planner<QueryPlanResult>>,
        cache: HashMap<String, Response>,
    ) -> Self {
        let this = Self::with_capacity(planner, cache.len().try_into().unwrap()).await;
        for (query, response) in cache.into_iter() {
            this.cache.insert(query, response).await;
        }
        this
    }
    pub(crate) async fn execute(&self, query: String) -> Result<Response, IntrospectionError> {
        if let Some(response) = self.cache.get(&query).await {
            return Ok(response);
        }
        let response =
            self.planner
                .introspect(query.clone())
                .await
                .map_err(|_e| IntrospectionError {
                    message: String::from("cannot find the introspection response").into(),
                })?;
        let introspection_result = response.into_result().map_err(|err| IntrospectionError {
            message: format!(
                "introspection error : {}",
                err.into_iter()
                    .map(|err| err.to_string())
                    .collect::<Vec<String>>()
                    .join(", "),
            )
            .into(),
        })?;
        let response = Response::builder().data(introspection_result).build();
        self.cache.insert(query, response.clone()).await;
        Ok(response)
    }
}
#[cfg(test)]
mod introspection_tests {
    use std::sync::Arc;
    use router_bridge::planner::IncrementalDeliverySupport;
    use router_bridge::planner::QueryPlannerConfig;
    use super::*;
    #[tokio::test]
    async fn test_plan_cache() {
        let query_to_test = r#"{
            __schema {
              types {
                name
              }
            }
          }"#;
        let schema = include_str!("../tests/fixtures/supergraph.graphql");
        let expected_data = Response::builder().data(42).build();
        let planner = Arc::new(
            Planner::new(
                schema.to_string(),
                QueryPlannerConfig {
                    incremental_delivery: Some(IncrementalDeliverySupport {
                        enable_defer: Some(true),
                    }),
                },
            )
            .await
            .unwrap(),
        );
        let cache = [(query_to_test.to_string(), expected_data.clone())]
            .iter()
            .cloned()
            .collect();
        let introspection = Introspection::from_cache(planner, cache).await;
        assert_eq!(
            expected_data,
            introspection
                .execute(query_to_test.to_string())
                .await
                .unwrap()
        );
    }
}