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

pub use noema_macros::keyed_service;

use crate::core::{Container, Injectable};

pub trait ToService<K: Eq + std::hash::Hash, S: ?Sized + Send + Sync> {
    fn to_service(self) -> Arc<S>;
}

pub trait KeyedServicesLock<K, S>
where
    K: Eq + std::hash::Hash,
    S: ?Sized + Send + Sync,
{
    fn lock(&self) -> &OnceLock<Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>>;
    fn set(&self, values: Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>) {
        if self.lock().get().is_some() {
            panic!(
                "Values already set for this KeyedServicesLock<Key={},Value={}>",
                std::any::type_name::<K>(),
                std::any::type_name::<S>()
            );
        }
        self.lock().set(values).ok();
    }
    fn values(&self) -> Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>> {
        self.lock()
            .get()
            .expect(
                format!(
                    "Values not set for this KeyedServicesLock<Key={},Value={}>",
                    std::any::type_name::<K>(),
                    std::any::type_name::<S>()
                )
                .as_str(),
            )
            .clone()
    }
}

/// Keyed Services of type S identified by key of type K
/// The services are provided via factory functions to allow for lazy instantiation
pub trait KeyedServices<K, S>
where
    K: Eq + std::hash::Hash,
    S: ?Sized + Send + Sync,
{
    /// Get one service by key
    fn one(&self, key: K) -> Option<Arc<S>> {
        Some(self.all().get(&key)?())
    }

    /// Find services by predicate
    fn find(&self, predicate: &dyn Fn(&Arc<K>) -> bool) -> HashMap<Arc<K>, Arc<S>> {
        let mut result = HashMap::new();
        for (k, v) in self.all().iter() {
            if predicate(&k) {
                result.insert(k.clone(), v());
            }
        }
        result
    }

    /// Get all services as a map of key to service
    fn all(&self) -> Arc<HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>>;
}

/// Get keyed services for key type K and service type S
/// # Examples
/// ```ignore
/// let locator = services::<MyKeyType, dyn MyServiceTrait + Send + Sync>();
/// let service = locator.one(MyKey1{value: 1}).unwrap();
/// ```
pub fn services<K, S>() -> impl KeyedServices<K, S>
where
    K: Eq + std::hash::Hash + 'static,
    S: ?Sized + Send + Sync + 'static,
    Container: KeyedServices<K, S>,
{
    return Container;
}

/// KeyedServicesBuilder is used to build the map of keyed services and set it in the container
/// It allows adding services by key and then building the final map of services
/// Usage:
/// ```ignore
/// KeyedServicesBuilder::<MyKeyType, dyn MyServiceTrait + Send + Sync>::new()
///     .add::<MyServiceImpl1>(MyKey1{value: 1})
///     .add::<MyServiceImpl2>(MyKey2{value: 2})
///     .build();
/// ```
pub struct KeyedServicesBuilder<K, S>
where
    K: Eq + std::hash::Hash,
    S: ?Sized + Send + Sync,
{
    map: HashMap<Arc<K>, Arc<dyn Fn() -> Arc<S> + Send + Sync>>,
}

impl<K, S> KeyedServicesBuilder<K, S>
where
    K: Eq + std::hash::Hash,
    S: ?Sized + Send + Sync,
{
    pub fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    pub fn add<T: ToService<K, S> + Injectable<Container> + Send + Sync>(mut self, key: K) -> Self {
        self.map.insert(
            Arc::new(key),
            Arc::new(|| T::inject(&Container).to_service()),
        );
        self
    }

    pub fn build(self)
    where
        Container: KeyedServicesLock<K, S>,
    {
        Container.set(Arc::new(self.map));
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn keyed_service_test() {
        use crate::core::{Container, Injectable, arc_dyn, shared_dyn};
        use crate::keyed_services::{
            KeyedServices, KeyedServicesBuilder, KeyedServicesLock, ToService, keyed_service,
            services,
        };
        use std::sync::Arc;

        #[keyed_service(i32)]
        #[keyed_service(String)]
        trait MyService: Send + Sync + 'static {
            fn get_value(&self) -> i32;
        }

        struct MyServiceImpl1;
        impl MyService for MyServiceImpl1 {
            fn get_value(&self) -> i32 {
                10
            }
        }
        impl Injectable<Container> for MyServiceImpl1 {
            fn inject(_: &Container) -> Self {
                MyServiceImpl1 {}
            }
        }

        struct MyServiceImpl2;
        impl MyService for MyServiceImpl2 {
            fn get_value(&self) -> i32 {
                20
            }
        }
        impl Injectable<Container> for MyServiceImpl2 {
            fn inject(_: &Container) -> Self {
                MyServiceImpl2 {}
            }
        }

        #[derive(Injectable)]
        struct TestGetKeyedServices {
            value: arc_dyn!(KeyedServices<i32, shared_dyn!(MyService)>),
        }

        KeyedServicesBuilder::<i32, dyn MyService + Send + Sync>::new()
            .add::<MyServiceImpl1>(1)
            .add::<MyServiceImpl2>(2)
            .build();

        KeyedServicesBuilder::<String, dyn MyService + Send + Sync>::new()
            .add::<MyServiceImpl1>("A".to_string())
            .build();

        let test = TestGetKeyedServices::inject(&Container);
        assert_eq!(test.value.one(1).unwrap().get_value(), 10);

        let locator_int = services::<i32, shared_dyn!(MyService)>();
        let service1 = locator_int.one(1).unwrap();
        let service2 = locator_int.one(2).unwrap();
        let locator_str = services::<String, dyn MyService + Send + Sync>();
        let service_a = locator_str.one("A".to_string()).unwrap();

        assert_eq!(service1.get_value(), 10);
        assert_eq!(service2.get_value(), 20);
        assert!(service_a.get_value() == 10);
    }
}
