use async_trait::async_trait;
use std::future::Future;

use crate::context::CommandContext;
use crate::extractor::{FromCommandArgs, NoArgsBody};
use crate::response::CommandResult;

/// Trait for asynchronous command handlers.
#[async_trait]
pub trait CommandHandlerAsync<T>: Send + Sync + 'static {
    async fn call<'a>(&self, key: &'a str, ctx: CommandContext<'a>) -> CommandResult;
}

macro_rules! impl_command_handler_async {
    ([$($arg:ident),*]) => {
        #[async_trait]
        impl<F, Fut, $($arg),*> CommandHandlerAsync<($($arg),*)> for F
        where
            F: Fn($($arg),*) -> Fut + Send + Sync + 'static,
            Fut: Future<Output = CommandResult> + Send,
            $( $arg: FromCommandArgs + Send, )*
        {
            async fn call<'a>(&self, key: &'a str, ctx: CommandContext<'a>) -> CommandResult {
                (self)($(<$arg>::from_args(key, &ctx)?),*).await
            }
        }
    };
    ($arg:ident) => {
        #[async_trait]
        impl<F, Fut, $arg> CommandHandlerAsync<$arg> for F
        where
            F: Fn($arg) -> Fut + Send + Sync + 'static,
            Fut: Future<Output = CommandResult> + Send,
            $arg: FromCommandArgs + Send,
        {
            async fn call<'a>(&self, key: &'a str, ctx: CommandContext<'a>) -> CommandResult {
                (self)(<$arg>::from_args(key, &ctx)?).await
            }
        }
    };
    () => {
        #[async_trait]
        impl<F, Fut> CommandHandlerAsync<NoArgsBody> for F
        where
            F: Fn() -> Fut + Send + Sync + 'static,
            Fut: Future<Output = CommandResult> + Send,
        {
            async fn call<'a>(&self, _: &'a str, _: CommandContext<'a>) -> CommandResult {
                (self)().await
            }
        }
    };
}

impl_command_handler_async!();
impl_command_handler_async!(T);
impl_command_handler_async!([T, T1]);
impl_command_handler_async!([T, T1, T2]);
impl_command_handler_async!([T, T1, T2, T3]);
impl_command_handler_async!([T, T1, T2, T3, T4, T5]);
impl_command_handler_async!([T, T1, T2, T3, T4, T5, T6]);
impl_command_handler_async!([T, T1, T2, T3, T4, T5, T6, T7]);
impl_command_handler_async!([T, T1, T2, T3, T4, T5, T6, T7, T8]);
impl_command_handler_async!([T, T1, T2, T3, T4, T5, T6, T7, T8, T9]);
impl_command_handler_async!([T, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]);
