use std::marker::PhantomData;
use std::sync::Arc;

use async_trait::async_trait;

use crate::di_container::asynchronous::IAsyncDIContainer;
use crate::errors::injectable::InjectableError;
use crate::interfaces::async_injectable::AsyncInjectable;
use crate::ptr::{ThreadsafeSingletonPtr, TransientPtr};

#[derive(strum_macros::Display, Debug)]
pub enum AsyncProvidable<DIContainerType>
where
    DIContainerType: IAsyncDIContainer,
{
    Transient(TransientPtr<dyn AsyncInjectable<DIContainerType>>),
    Singleton(ThreadsafeSingletonPtr<dyn AsyncInjectable<DIContainerType>>),
    #[cfg(feature = "factory")]
    Factory(
        crate::ptr::ThreadsafeFactoryPtr<
            dyn crate::interfaces::any_factory::AnyThreadsafeFactory,
        >,
    ),
    #[cfg(feature = "factory")]
    DefaultFactory(
        crate::ptr::ThreadsafeFactoryPtr<
            dyn crate::interfaces::any_factory::AnyThreadsafeFactory,
        >,
    ),
    #[cfg(feature = "factory")]
    AsyncDefaultFactory(
        crate::ptr::ThreadsafeFactoryPtr<
            dyn crate::interfaces::any_factory::AnyThreadsafeFactory,
        >,
    ),
}

#[async_trait]
pub trait IAsyncProvider<DIContainerType>: Send + Sync
where
    DIContainerType: IAsyncDIContainer,
{
    async fn provide(
        &self,
        di_container: &Arc<DIContainerType>,
        dependency_history: Vec<&'static str>,
    ) -> Result<AsyncProvidable<DIContainerType>, InjectableError>;

    fn do_clone(&self) -> Box<dyn IAsyncProvider<DIContainerType>>;
}

impl<DIContainerType> Clone for Box<dyn IAsyncProvider<DIContainerType>>
where
    DIContainerType: IAsyncDIContainer,
{
    fn clone(&self) -> Self
    {
        self.do_clone()
    }
}

pub struct AsyncTransientTypeProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    injectable_phantom: PhantomData<InjectableType>,
    di_container_phantom: PhantomData<DIContainerType>,
}

impl<InjectableType, DIContainerType>
    AsyncTransientTypeProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    pub fn new() -> Self
    {
        Self {
            injectable_phantom: PhantomData,
            di_container_phantom: PhantomData,
        }
    }
}

#[async_trait]
impl<InjectableType, DIContainerType> IAsyncProvider<DIContainerType>
    for AsyncTransientTypeProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    async fn provide(
        &self,
        di_container: &Arc<DIContainerType>,
        dependency_history: Vec<&'static str>,
    ) -> Result<AsyncProvidable<DIContainerType>, InjectableError>
    {
        Ok(AsyncProvidable::Transient(
            InjectableType::resolve(di_container, dependency_history).await?,
        ))
    }

    fn do_clone(&self) -> Box<dyn IAsyncProvider<DIContainerType>>
    {
        Box::new(self.clone())
    }
}

impl<InjectableType, DIContainerType> Clone
    for AsyncTransientTypeProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    fn clone(&self) -> Self
    {
        Self {
            injectable_phantom: self.injectable_phantom,
            di_container_phantom: self.di_container_phantom,
        }
    }
}

pub struct AsyncSingletonProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    singleton: ThreadsafeSingletonPtr<InjectableType>,

    di_container_phantom: PhantomData<DIContainerType>,
}

impl<InjectableType, DIContainerType>
    AsyncSingletonProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    pub fn new(singleton: ThreadsafeSingletonPtr<InjectableType>) -> Self
    {
        Self {
            singleton,
            di_container_phantom: PhantomData,
        }
    }
}

#[async_trait]
impl<InjectableType, DIContainerType> IAsyncProvider<DIContainerType>
    for AsyncSingletonProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    async fn provide(
        &self,
        _di_container: &Arc<DIContainerType>,
        _dependency_history: Vec<&'static str>,
    ) -> Result<AsyncProvidable<DIContainerType>, InjectableError>
    {
        Ok(AsyncProvidable::Singleton(self.singleton.clone()))
    }

    fn do_clone(&self) -> Box<dyn IAsyncProvider<DIContainerType>>
    {
        Box::new(self.clone())
    }
}

impl<InjectableType, DIContainerType> Clone
    for AsyncSingletonProvider<InjectableType, DIContainerType>
where
    InjectableType: AsyncInjectable<DIContainerType>,
    DIContainerType: IAsyncDIContainer,
{
    fn clone(&self) -> Self
    {
        Self {
            singleton: self.singleton.clone(),
            di_container_phantom: PhantomData,
        }
    }
}

#[derive(Clone, Copy)]
pub enum AsyncFactoryVariant
{
    Normal,
    Default,
    AsyncDefault,
}

#[cfg(feature = "factory")]
pub struct AsyncFactoryProvider
{
    factory: crate::ptr::ThreadsafeFactoryPtr<
        dyn crate::interfaces::any_factory::AnyThreadsafeFactory,
    >,
    variant: AsyncFactoryVariant,
}

#[cfg(feature = "factory")]
impl AsyncFactoryProvider
{
    pub fn new(
        factory: crate::ptr::ThreadsafeFactoryPtr<
            dyn crate::interfaces::any_factory::AnyThreadsafeFactory,
        >,
        variant: AsyncFactoryVariant,
    ) -> Self
    {
        Self { factory, variant }
    }
}

#[cfg(feature = "factory")]
#[async_trait]
impl<DIContainerType> IAsyncProvider<DIContainerType> for AsyncFactoryProvider
where
    DIContainerType: IAsyncDIContainer,
{
    async fn provide(
        &self,
        _di_container: &Arc<DIContainerType>,
        _dependency_history: Vec<&'static str>,
    ) -> Result<AsyncProvidable<DIContainerType>, InjectableError>
    {
        Ok(match self.variant {
            AsyncFactoryVariant::Normal => AsyncProvidable::Factory(self.factory.clone()),
            AsyncFactoryVariant::Default => {
                AsyncProvidable::DefaultFactory(self.factory.clone())
            }
            AsyncFactoryVariant::AsyncDefault => {
                AsyncProvidable::AsyncDefaultFactory(self.factory.clone())
            }
        })
    }

    fn do_clone(&self) -> Box<dyn IAsyncProvider<DIContainerType>>
    {
        Box::new(self.clone())
    }
}

#[cfg(feature = "factory")]
impl Clone for AsyncFactoryProvider
{
    fn clone(&self) -> Self
    {
        Self {
            factory: self.factory.clone(),
            variant: self.variant,
        }
    }
}