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

use crate::{
    root_service_provider::RootServiceProvider,
    service::{Service, ServiceProvider},
    service_lifetime::ServiceLifetime,
};

use super::error::Error;

/// Service provider that layers a per-scope cache on top of a shared
/// [`RootServiceProvider`].
///
/// Scoped services are cached in `services` for the lifetime of the scope.
/// Singleton and unmanaged services are delegated to the root provider so
/// every scope sees the same instance. Transient services are always built
/// fresh and never cached.
pub struct ScopedServiceProvider {
    services: RwLock<HashMap<String, Arc<dyn Service>>>,
    root: Arc<RootServiceProvider>,
}

impl ScopedServiceProvider {
    /// Create service manger from service collection.
    pub fn new(root: Arc<RootServiceProvider>) -> Self {
        ScopedServiceProvider {
            services: RwLock::new(HashMap::new()),
            root,
        }
    }
}

impl ServiceProvider for ScopedServiceProvider {
    /// Get or create an instance
    fn get_or_create_instance(&self, type_name: &str) -> Result<Arc<dyn Service>, Error> {
        // Get service definition
        let service_definition = self
            .get_service_container()
            .get_service_definition_from_key(type_name)
            .ok_or_else(|| Error::Internal("Service definition not found".to_string()))?;

        // Try getting the service from the cache
        let services = self.services.read().unwrap();

        if let Some(service) = services.get(type_name) {
            return Ok(service.clone());
        }

        drop(services);

        // Create a new service instance
        let service = (service_definition.init)(self.as_service_provider());

        match service_definition.lifetime {
            // Add new instance for scoped and singleton
            ServiceLifetime::Singleton | ServiceLifetime::Unmanaged => {
                self.root.get_or_create_instance(type_name)
            }

            ServiceLifetime::Scoped => {
                self.services
                    .write()
                    .unwrap()
                    .insert(type_name.to_string(), service.clone());

                Ok(service)
            }
            // Transient: build a new instance on every call, no caching.
            ServiceLifetime::Transient => Ok(service),
        }
    }

    fn as_service_provider(&self) -> &dyn ServiceProvider {
        self
    }

    fn get_service_container(&self) -> &crate::service_container::ServiceContainer {
        &self.root.get_service_container()
    }
}
