use std::{collections::HashMap, iter, sync::Arc};

use async_trait::async_trait;
use serde::{
    Deserialize,
    de::{DeserializeOwned, IgnoredAny},
};
use kameleoon_trace_logging::trace_method;

use crate::{
    data::{BaseData, CustomData},
    debug_log,
    error::{ErrorCode, KErr},
    logging::LogLevel,
    network::{network_manager::data_api::DataApiNetworkManager, response::Response},
    storage::visitor_manager::{AddDataOptions, VisitorManager},
    utils::{KRes, visitor_code_validator::VisitorCodeValidator},
};

#[async_trait]
pub(crate) trait RemoteDataManager: Send + Sync {
    async fn fetch_raw(&self, key: &str) -> KRes<Vec<u8>>;

    async fn fetch<T: DeserializeOwned>(&self, key: &str) -> KRes<T>;

    async fn fetch_visitor_warehouse_audience(
        &self,
        visitor_code: &str,
        warehouse_key: Option<&str>,
        custom_data_index: u32,
    ) -> KRes<()>;
}

pub(crate) struct RemoteDataManagerImpl<V, N> {
    visitor_manager: Arc<V>,
    data_api: Arc<N>,
}

#[async_trait]
impl<V: VisitorManager, N: DataApiNetworkManager> RemoteDataManager for RemoteDataManagerImpl<V, N> {
    async fn fetch_raw(&self, key: &str) -> KRes<Vec<u8>> {
        //~ [42126]
        debug_log!("CALL async RemoteDataManagerImpl::fetch_raw(key: {key})");
        let result = self._fetch_raw(key).await;
        debug_log!("RET async RemoteDataManagerImpl::fetch_raw(key: {key}) → {:?}", result);
        result
    }

    async fn fetch<T: DeserializeOwned>(&self, key: &str) -> KRes<T> {
        //~ [42126]
        debug_log!("CALL async RemoteDataManagerImpl::fetch(key: {key})");
        let result = self._fetch(key).await;
        debug_log!("RET async RemoteDataManagerImpl::fetch(key: {key})");
        result
    }

    async fn fetch_visitor_warehouse_audience(
        &self,
        visitor_code: &str,
        warehouse_key: Option<&str>,
        custom_data_index: u32,
    ) -> KRes<()> {
        //~ [42126]
        debug_log!(
            "CALL async RemoteDataManagerImpl::fetch_visitor_warehouse_audience(\
            visitor_code: {visitor_code}, warehouse_key: {:?}, custom_data_index: {custom_data_index})",
            warehouse_key
        );
        let result = self._fetch_visitor_warehouse_audience(visitor_code, warehouse_key, custom_data_index).await;
        debug_log!(
            "RET async RemoteDataManagerImpl::fetch_visitor_warehouse_audience(\
            visitor_code: {visitor_code}, warehouse_key: {:?}, custom_data_index: {custom_data_index}) → {:?}",
            warehouse_key,
            result
        );
        result
    }
}

impl<V: VisitorManager, N: DataApiNetworkManager> RemoteDataManagerImpl<V, N> {
    #[trace_method(LogLevel::Debug, skip(retval, visitor_manager, data_api))]
    pub fn new(visitor_manager: Arc<V>, data_api: Arc<N>) -> Self {
        Self {
            visitor_manager,
            data_api,
        }
    }

    async fn _fetch_raw(&self, key: &str) -> KRes<Vec<u8>> {
        Ok(self.fetch_resp(key).await?.body)
    }

    async fn _fetch<T: DeserializeOwned>(&self, key: &str) -> KRes<T> {
        Ok(self.fetch_resp(key).await?.json()?)
    }

    async fn _fetch_visitor_warehouse_audience(
        &self,
        visitor_code: &str,
        warehouse_key: Option<&str>,
        custom_data_index: u32,
    ) -> KRes<()> {
        VisitorCodeValidator::validate(visitor_code)?;
        let remote_data_key = warehouse_key.map_or(visitor_code, |wk| if wk.is_empty() { visitor_code } else { wk });
        let wa: WarehouseAudiences = self.fetch(remote_data_key).await?;
        let data_values = wa.warehouse_audiences.map_or_else(Vec::new, |wa| wa.into_keys().collect());
        let wa_data = CustomData::new_with_index(custom_data_index, data_values);
        self.visitor_manager.add_data(
            visitor_code,
            iter::once(BaseData::CustomData(wa_data)),
            AddDataOptions {
                overwrite: true,
                track: true,
            },
        );
        return Ok(());
    }

    async fn fetch_resp(&self, key: &str) -> KRes<Response> {
        let response = self.data_api.get_remote_data(key).await?;
        if response.status_code / 100 != 2 {
            let msg = format!("Failed to fetch remote data: unexpected status code {}", response.status_code);
            return Err(KErr::new(ErrorCode::Network, msg));
        }
        return Ok(response);
    }
}

#[derive(Debug, Deserialize)]
pub(crate) struct WarehouseAudiences {
    #[serde(rename = "warehouseAudiences")]
    pub warehouse_audiences: Option<HashMap<String, IgnoredAny>>,
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use std::{fmt::Debug, mem};

    use crate::{
        datafile::{
            datafile::{DataFile, DataFileImpl},
            datafile_manager::MockDataFileManager,
        },
        network::{network_manager::data_api::MockDataApiNetworkManager, response::Response},
        storage::visitor_manager::VisitorManagerImpl,
        utils::test_utils,
    };
    use tokio::runtime::Handle;

    use super::*;
    use arc_swap::Guard;
    use rstest::*;

    fn checkpoint_data_api_mock<V>(mut remote_data_manager: RemoteDataManagerImpl<V, MockDataApiNetworkManager>) {
        let data_api = mem::replace(&mut remote_data_manager.data_api, Arc::new(MockDataApiNetworkManager::new()));
        let mut data_api = Arc::into_inner(data_api).unwrap();
        data_api.checkpoint();
    }

    #[rstest]
    #[case("{\"a\":1,\"b\":2}", Err::<HashMap<String, String>, ErrorCode>(ErrorCode::Network))]
    #[case("{\"a\":1,\"b\":2}", Ok(HashMap::from([("a".to_owned(), 1), ("b".to_owned(), 2)])))]
    #[tokio::test]
    async fn test_fetch<T: DeserializeOwned + PartialEq + Debug>(
        #[case] response_body: String,
        #[case] expected: Result<T, ErrorCode>,
    ) {
        // arrange
        const KEY: &str = "key123";
        let datafile_manager = Arc::new(MockDataFileManager::new());
        let visitor_manager = Arc::new(VisitorManagerImpl::new(datafile_manager, Handle::current()));
        let mut data_api = MockDataApiNetworkManager::new();
        data_api.expect_get_remote_data().once().returning(move |actual_key| {
            assert_eq!(KEY, actual_key);
            let response = Response {
                status_code: 200,
                body: response_body.to_owned().into_bytes(),
                headers: vec![],
            };
            Box::pin(std::future::ready(Ok(response)))
        });
        let data_api = Arc::new(data_api);
        let remote_data_manager = RemoteDataManagerImpl::new(visitor_manager, data_api);
        // act
        let actual = remote_data_manager.fetch::<T>(KEY).await;
        // assert
        match expected {
            Ok(expected) => assert_eq!(expected, actual.unwrap()),
            Err(expected) => assert_eq!(expected, actual.unwrap_err().code()),
        }
        checkpoint_data_api_mock(remote_data_manager);
    }

    #[rstest]
    #[case("vc6", Some(""), "vc6", Some("warehouse_visitor_audiences"), Ok(vec!["399".to_owned(), "5993".to_owned()]))]
    #[case("vc6", None, "vc6", Some("warehouse_visitor_audiences"), Ok(vec!["399".to_owned(), "5993".to_owned()]))]
    #[case(
        "vc6", Some("warehouse_key"), "warehouse_key", Some("warehouse_warehouse_key"),
        Ok(vec!["1165".to_owned(), "4567".to_owned()])
    )]
    #[case("vc6", None, "vc6", Some("warehouse_visitor_audiences_null_keys"), Ok(vec![]))]
    #[case("vc6", None, "vc6", Some("warehouse_visitor_audiences_no_keys"), Ok(vec![]))]
    #[case("vc6", None, "vc6", None, Err(ErrorCode::Network))]
    #[tokio::test]
    async fn test_fetch_visitor_warehouse_audience(
        #[case] visitor_code: &str,
        #[case] warehouse_key: Option<&str>,
        #[case] remote_data_key: &str,
        #[case] response_file_name: Option<&str>,
        #[case] expected: Result<Vec<String>, ErrorCode>,
    ) {
        // arrange
        const CD_INDEX: u32 = 98;
        let mut datafile_manager = MockDataFileManager::new();
        datafile_manager.expect_datafile().returning(|| Guard::from_inner(Arc::new(DataFileImpl::new_nullum(None))));
        let visitor_manager = Arc::new(VisitorManagerImpl::new(Arc::new(datafile_manager), Handle::current()));
        let mut data_api = MockDataApiNetworkManager::new();
        let remote_data_key = remote_data_key.to_owned();
        let response_file_name = response_file_name.map(|name| name.to_owned());
        data_api.expect_get_remote_data().once().returning(move |actual_key| {
            assert_eq!(remote_data_key, actual_key);
            let (response_body, status_code) = response_file_name.as_ref().map_or_else(
                || (vec![], 500),
                |name| (test_utils::load_file(&format!("resources/warehouse/{name}.json")).into_bytes(), 200),
            );
            let response = Response {
                status_code,
                body: response_body,
                headers: vec![],
            };
            Box::pin(std::future::ready(Ok(response)))
        });
        let data_api = Arc::new(data_api);
        let remote_data_manager = RemoteDataManagerImpl::new(visitor_manager, data_api);
        // act
        let actual = remote_data_manager.fetch_visitor_warehouse_audience(visitor_code, warehouse_key, CD_INDEX).await;
        // assert
        match expected {
            Ok(expected_values) => {
                assert_eq!(Ok(()), actual);
                let visitor = remote_data_manager.visitor_manager.get(visitor_code).unwrap();
                let visitor_data = visitor.rdata();
                assert_eq!(1, visitor_data.custom_data_map.r().len());
                let actual_wa_data = visitor_data.custom_data_map.r().get(&CD_INDEX).unwrap();
                let mut actual_values: Vec<String> = actual_wa_data.values.clone();
                actual_values.sort();
                assert_eq!(expected_values, actual_values);
            }
            Err(err) => assert_eq!(err, actual.unwrap_err().code()),
        }
        checkpoint_data_api_mock(remote_data_manager);
    }
}
