use std::{collections::HashMap, sync::Arc, time::Duration};

use async_trait::async_trait;
use futures::stream::BoxStream;
use hive_router_query_planner::planner::plan_nodes::CustomScalarPaths;
use http::{HeaderMap, Uri};
use sonic_rs::Value;

use crate::{
    executors::error::SubgraphExecutorError, plugin_context::PluginRequestState,
    response::subgraph_response::SubgraphResponse,
};

#[async_trait]
pub trait SubgraphExecutor {
    fn endpoint(&self) -> &Uri;

    async fn execute<'a>(
        &self,
        execution_request: SubgraphExecutionRequest<'a>,
        timeout: Option<Duration>,
        plugin_req_state: Option<&'a PluginRequestState<'a>>,
    ) -> Result<SubgraphResponse<'static>, SubgraphExecutorError>;

    async fn subscribe<'a>(
        &self,
        execution_request: SubgraphExecutionRequest<'a>,
        timeout: Option<Duration>,
    ) -> Result<
        BoxStream<'static, Result<SubgraphResponse<'static>, SubgraphExecutorError>>,
        SubgraphExecutorError,
    >;

    fn to_boxed_arc<'a>(self) -> Arc<Box<dyn SubgraphExecutor + Send + Sync + 'a>>
    where
        Self: Sized + Send + Sync + 'a,
    {
        Arc::new(Box::new(self))
    }
}

pub type SubgraphExecutorType = dyn crate::executors::common::SubgraphExecutor + Send + Sync;

pub type SubgraphExecutorBoxedArc = Arc<Box<SubgraphExecutorType>>;

pub type SubgraphRequestExtensions = HashMap<String, Value>;

pub struct SubgraphExecutionRequest<'a> {
    /// Holds the query string to be executed.
    /// The query string contains an anonymous GraphQL document.
    /// The name is during execution at `document_name_write_pos` position in the query string.
    pub query: &'a str,
    pub document_name_write_pos: usize,
    pub dedupe: bool,
    pub operation_name: Option<String>,
    // TODO: variables could be stringified before even executing the request
    pub variables: Option<HashMap<&'a str, &'a sonic_rs::Value>>,
    pub headers: HeaderMap,
    pub raw_variable_values: Option<Vec<(&'a str, Vec<u8>)>>,
    pub extensions: Option<SubgraphRequestExtensions>,
    pub custom_scalar_paths: Option<&'a CustomScalarPaths>,
}

impl SubgraphExecutionRequest<'_> {
    pub fn add_request_extensions_field(&mut self, key: String, value: Value) {
        self.extensions
            .get_or_insert_with(HashMap::new)
            .insert(key, value);
    }
}
