use async_graphql::dynamic::FieldValue;
use bson::Document;
use http::{header::HeaderName, HeaderMap, HeaderValue};
use log::{debug, trace};
use reqwest::Client;

use crate::{
    configuration::subgraph::{
        data_sources::http::{DefaultHeader, HttpDataSourceConfig},
        entities::ServiceEntityConfig,
        SubGraphConfig,
    },
    graphql::entity::create_return_types::{ResolverResponse, ResolverResponseMeta},
    resolver_type::ResolverType,
};

use super::DataSource;
pub mod filter;
pub mod services;

#[derive(Debug, Clone)]
pub struct HttpDataSource {
    pub client: Client,
    pub config: HttpDataSourceConfig,
}

impl HttpDataSource {
    fn get_headers(default_headers: Option<&Vec<DefaultHeader>>) -> HeaderMap {
        let mut headers = HeaderMap::new();

        if default_headers.is_some() {
            let cloned_default_headers = default_headers.unwrap().clone();

            for default_header in cloned_default_headers {
                let header_name = HeaderName::from_bytes(default_header.name.as_bytes()).unwrap();
                let header_value =
                    HeaderValue::from_bytes(default_header.value.as_bytes()).unwrap();
                headers.insert(header_name, header_value);
            }
        }

        headers
    }

    pub async fn init(http_data_source_config: &HttpDataSourceConfig) -> DataSource {
        let header_config = http_data_source_config.default_headers.as_ref();
        let headers = HttpDataSource::get_headers(header_config.clone());
        let client = Client::builder().default_headers(headers).build();

        match client {
            Ok(client) => DataSource::HTTP(HttpDataSource {
                client,
                config: http_data_source_config.clone(),
            }),
            Err(error) => {
                log::error!("Failed to build HTTP Client.");
                debug!("{:?}", error);
                panic!()
            }
        }
    }

    pub async fn execute_operation<'a>(
        data_source: &DataSource,
        input: Document,
        entity: ServiceEntityConfig,
        resolver_type: ResolverType,
        subgraph_config: &SubGraphConfig,
    ) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
        debug!("Executing HTTP Data Source Operation");

        let data_source = match data_source {
            DataSource::HTTP(ds) => ds,
            _ => unreachable!(),
        };

        trace!("HTTP Data Source: {:?}", data_source);

        let filter =
            HttpDataSource::create_filter(data_source, input, &entity, resolver_type).await?;

        trace!("Filter Created: {:?}", filter);

        match resolver_type {
            ResolverType::FindOne => {
                let result =
                    services::Services::find_one(data_source.client.clone(), filter).await?;
                let res = ResolverResponse {
                    data: vec![FieldValue::owned_any(result)],
                    meta: ResolverResponseMeta {
                        request_id: uuid::Uuid::new_v4().to_string(),
                        service_name: subgraph_config.service.name.clone(),
                        service_version: subgraph_config.service.version.clone(),
                        executed_at: chrono::Utc::now()
                            .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
                        count: 1,
                        total_count: 1,
                        page: 1,
                        total_pages: 1,
                        user_uuid: None,
                    },
                };
                Ok(Some(FieldValue::owned_any(res)))
            }
            ResolverType::FindMany => {
                let results =
                    services::Services::find_many(data_source.client.clone(), filter).await?;
                let count = results.len();
                let res = ResolverResponse {
                    data: results
                        .into_iter()
                        .map(|doc| FieldValue::owned_any(doc))
                        .collect(),
                    meta: ResolverResponseMeta {
                        request_id: uuid::Uuid::new_v4().to_string(),
                        service_name: subgraph_config.service.name.clone(),
                        service_version: subgraph_config.service.version.clone(),
                        executed_at: chrono::Utc::now()
                            .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
                        count: count as i64,
                        total_count: count as i64,
                        page: 1,
                        total_pages: 1,
                        user_uuid: None,
                    },
                };
                Ok(Some(FieldValue::owned_any(res)))
            }
            ResolverType::CreateOne => {
                let result =
                    services::Services::create_one(data_source.client.clone(), filter).await?;
                let res = ResolverResponse {
                    data: vec![FieldValue::owned_any(result)],
                    meta: ResolverResponseMeta {
                        request_id: uuid::Uuid::new_v4().to_string(),
                        service_name: subgraph_config.service.name.clone(),
                        service_version: subgraph_config.service.version.clone(),
                        executed_at: chrono::Utc::now()
                            .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
                        count: 1,
                        total_count: 1,
                        page: 1,
                        total_pages: 1,
                        user_uuid: None,
                    },
                };
                Ok(Some(FieldValue::owned_any(res)))
            }
            ResolverType::UpdateOne => {
                let result =
                    services::Services::update_one(data_source.client.clone(), filter).await?;
                let res = ResolverResponse {
                    data: vec![FieldValue::owned_any(result)],
                    meta: ResolverResponseMeta {
                        request_id: uuid::Uuid::new_v4().to_string(),
                        service_name: subgraph_config.service.name.clone(),
                        service_version: subgraph_config.service.version.clone(),
                        executed_at: chrono::Utc::now()
                            .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
                        count: 1,
                        total_count: 1,
                        page: 1,
                        total_pages: 1,
                        user_uuid: None,
                    },
                };
                Ok(Some(FieldValue::owned_any(res)))
            }
            ResolverType::UpdateMany => {
                let results =
                    services::Services::update_many(data_source.client.clone(), filter).await?;
                let count = results.len();
                let res = ResolverResponse {
                    data: results
                        .into_iter()
                        .map(|doc| FieldValue::owned_any(doc))
                        .collect(),
                    meta: ResolverResponseMeta {
                        request_id: uuid::Uuid::new_v4().to_string(),
                        service_name: subgraph_config.service.name.clone(),
                        service_version: subgraph_config.service.version.clone(),
                        executed_at: chrono::Utc::now()
                            .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
                        count: count as i64,
                        total_count: count as i64,
                        page: 1,
                        total_pages: 1,
                        user_uuid: None,
                    },
                };
                Ok(Some(FieldValue::owned_any(res)))
            }
            _ => panic!("Invalid resolver type"),
        }
    }
}
