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

use crate::{
    scoped_service_provider::ScopedServiceProvider,
    service::{Service, ServiceProvider},
    service_lifetime::ServiceLifetime,
};

use super::{error::Error, service_container::ServiceContainer};

/// Service provider providing singleton and unmanaged services.
/// Owns the [`ServiceContainer`] and holds the cache of singleton instances shared across all scopes it creates.
pub struct RootServiceProvider {
    service_container: ServiceContainer,
    singleton_services: RwLock<HashMap<String, Arc<dyn Service>>>,
}

impl RootServiceProvider {
    /// Create a root service provider from a populated service container.
    pub fn new(sc: ServiceContainer) -> Self {
        Self {
            service_container: sc,
            singleton_services: RwLock::new(HashMap::new()),
        }
    }

    /// Create a scoped service provider backed by this root provider.
    /// Scoped services live for the lifetime of the returned scope.
    pub fn create_scope(self) -> ScopedServiceProvider {
        ScopedServiceProvider::new(Arc::new(self))
    }
}

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

    fn get_or_create_instance(&self, type_name: &str) -> Result<Arc<dyn Service>, Error> {
        // Get service definition
        let service_definition = self
            .service_container
            .get_service_definition_from_key(type_name)
            .ok_or_else(|| Error::Internal("Service definition not found".to_string()))?;

        // If the service instance exists, return it
        match service_definition.lifetime {
            // Retrieve from the unmanaged services
            ServiceLifetime::Unmanaged => {
                let unmanaged_services = &self.service_container.unmanaged_services;

                if unmanaged_services.contains_key(type_name) {
                    let srv = unmanaged_services.get(type_name);

                    return match srv {
                        Some(service) => Ok(service.clone()),
                        None => Err(Error::Internal(format!(
                            "Cannot get the service instance for {}",
                            type_name
                        ))),
                    };
                } else {
                    return Err(Error::Internal(format!(
                        "Unmanaged instance for {} is not set",
                        type_name,
                    )));
                }
            }
            // Retrieve from singleton
            ServiceLifetime::Singleton => {
                // Try getting the service from the cache
                let singleton_services = self.singleton_services.read().unwrap();

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

                // Unlock the singleton services
                drop(singleton_services);

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

                // Add new instance for scoped and singleton
                self.singleton_services
                    .write()
                    .expect("blocked")
                    .insert(type_name.to_string(), service.clone());

                return Ok(service.clone());
            }
            // Scoped/transient services require a scope — the root provider
            // has no notion of per-scope state.
            // Scoped or transient services are not supported in root service provider
            // because it needs a scope. Instead, get the service from a service provider.
            ServiceLifetime::Scoped | ServiceLifetime::Transient => Err(Error::Internal(format!(
                "Cannot get the service instance for {} (scoped or transient services are not supported in root service provider)",
                type_name
            ))),
        }
    }

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