use std::{any::Any, collections::HashMap, sync::Arc};

use crate::{
    root_service_provider::RootServiceProvider,
    service::{Service, ServiceResolver},
    service_definition::{ServiceDefinition, ServiceInitFn},
    service_lifetime::ServiceLifetime,
};

/// Holds every service registration made by the user.
///
/// Once the registrations are complete, call [`ServiceContainer::build`] to
/// turn it into a [`RootServiceProvider`] that can resolve services.
pub struct ServiceContainer {
    service_collection: HashMap<String, ServiceDefinition>,
    /// Pre-built instances for services declared with `Unmanaged` lifetime.
    pub unmanaged_services: HashMap<String, Arc<dyn Service>>,
    /// Maps a trait's type name to its [`ServiceResolver`], erased as `Any`.
    pub trait_service_map: HashMap<String, Arc<dyn Any + Send + Sync + 'static>>,
}

impl Default for ServiceContainer {
    fn default() -> Self {
        Self::new()
    }
}

impl ServiceContainer {
    pub fn new() -> Self {
        Self {
            service_collection: HashMap::new(),
            unmanaged_services: HashMap::new(),
            trait_service_map: HashMap::new(),
        }
    }

    /// Declare a singleton in the service container.
    pub fn add_singleton<T: Service + 'static>(&mut self) {
        self.add_service::<T>(ServiceLifetime::Singleton, None);
    }

    /// Declare a singleton exposed through a trait.
    pub fn add_trait_singleton<I: ?Sized + Send + Sync + 'static, T: Service + 'static>(
        &mut self,
        resolver: ServiceResolver<I>,
    ) {
        self.add_trait_service::<I, T>(ServiceLifetime::Singleton, None, resolver);
    }

    /// Declare a scoped service in the service container.
    pub fn add_scoped<T: Service + 'static>(&mut self) {
        self.add_service::<T>(ServiceLifetime::Scoped, None);
    }

    /// Declare a scoped service exposed through a trait.
    pub fn add_trait_scoped<I: ?Sized + Send + Sync + 'static, T: Service + 'static>(
        &mut self,
        resolver: ServiceResolver<I>,
    ) {
        self.add_trait_service::<I, T>(ServiceLifetime::Scoped, None, resolver);
    }

    /// Declare a transient service in the service container.
    pub fn add_transient<T: Service + 'static>(&mut self) {
        self.add_service::<T>(ServiceLifetime::Transient, None);
    }

    /// Declare a transient service exposed through a trait.
    pub fn add_trait_transient<I: ?Sized + Send + Sync + 'static, T: Service + 'static>(
        &mut self,
        resolver: ServiceResolver<I>,
    ) {
        self.add_trait_service::<I, T>(ServiceLifetime::Transient, None, resolver);
    }

    /// Declare an unmanaged instance in the service container.
    pub fn add_unmanaged<T: Service + 'static>(&mut self, instance: T) {
        self.add_service::<T>(ServiceLifetime::Unmanaged, Some(instance));
    }

    /// Declare an unmanaged instance exposed through a trait.
    pub fn add_trait_unmanaged<I: ?Sized + Send + Sync + 'static, T: Service + 'static>(
        &mut self,
        resolver: ServiceResolver<I>,
        instance: T,
    ) {
        self.add_trait_service::<I, T>(ServiceLifetime::Unmanaged, Some(instance), resolver);
    }

    /// Register a trait service with its lifetime and optional pre-built instance.
    fn add_trait_service<I: ?Sized + Send + Sync + 'static, T: Service + 'static>(
        &mut self,
        lifetime: ServiceLifetime,
        instance: Option<T>,
        resolver: ServiceResolver<I>,
    ) {
        let trait_name = std::any::type_name::<I>().to_string();
        let service_instance: Option<Arc<dyn Service>> =
            instance.map(|s| Arc::new(s) as Arc<dyn Service>);

        // Add the resolver
        self.trait_service_map
            .insert(trait_name.clone(), Arc::new(resolver));

        // Add the service
        self.add_keyed_service(trait_name, lifetime, T::init, service_instance);
    }

    /// Add a service with its lifetime and instance
    fn add_service<T: Service + 'static>(
        &mut self,
        lifetime: ServiceLifetime,
        instance: Option<T>,
    ) {
        let type_name = std::any::type_name::<T>().to_string();
        let service_instance: Option<Arc<dyn Service>> = match instance {
            Some(service) => Some(Arc::new(service)),
            None => None,
        };

        self.add_keyed_service(type_name, lifetime, T::init, service_instance);
    }

    /// Add a service with its lifetime and instance
    fn add_keyed_service(
        &mut self,
        key: String,
        lifetime: ServiceLifetime,
        init: ServiceInitFn,
        instance: Option<Arc<dyn Service>>,
    ) {
        let service_definition = ServiceDefinition { init, lifetime };

        if matches!(lifetime, ServiceLifetime::Unmanaged)
            && let Some(service) = instance
        {
            self.unmanaged_services.insert(key.clone(), service);
        }

        self.service_collection.insert(key, service_definition);
    }

    /// Look up a service definition by its key (the type or trait name).
    pub fn get_service_definition_from_key(&self, type_name: &str) -> Option<&ServiceDefinition> {
        self.service_collection.get(type_name)
    }

    /// Consume the container and return a [`RootServiceProvider`] capable of
    /// resolving singleton and unmanaged services.
    pub fn build(self) -> RootServiceProvider {
        RootServiceProvider::new(self)
    }
}
