use crate::actor::Actor;
use crate::context::Context;
use crate::handler::Handler;
use crate::mailbox::Message;
use async_trait::async_trait;
use futures::channel::oneshot;
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;

#[async_trait]
trait TypeErasedEnvelope: Send {
    type Actor: Actor + Send;
    async fn handle(&mut self, actor: &mut Self::Actor, ctx: &mut Context<Self::Actor>);
}

#[derive(Debug)]
struct EnvelopeInner<A, M>
where
    M: Message,
    A: Actor,
    A: Handler<M>,
{
    actor: PhantomData<A>,
    msg: Option<M>,
    return_to: Option<oneshot::Sender<M::Return>>,
}

#[async_trait]
impl<A, M> TypeErasedEnvelope for EnvelopeInner<A, M>
where
    M: Message,
    A: Actor + Handler<M> + Send,
{
    type Actor = A;

    async fn handle(&mut self, actor: &mut Self::Actor, ctx: &mut Context<Self::Actor>) {
        if let Some(return_to) = self.return_to.take() {
            let result = Handler::handle(actor, self.msg.take().unwrap(), ctx).await;
            return_to.send(result).ok();
        }
    }
}

pub(crate) struct Envelope<A>
where
    A: 'static + Actor,
{
    inner: Box<dyn TypeErasedEnvelope<Actor = A>>,
}

impl<A> Debug for Envelope<A>
where
    A: 'static + Actor + Debug,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Envelope").finish()
    }
}

impl<A> Envelope<A>
where
    A: 'static + Actor + Send,
{
    pub fn pack<M>(message: M) -> (Self, oneshot::Receiver<M::Return>)
    where
        M: 'static + Message,
        A: Handler<M> + Send,
    {
        let (tx, rx) = oneshot::channel();
        (
            Envelope {
                inner: Box::new(EnvelopeInner {
                    actor: PhantomData,
                    msg: Some(message),
                    return_to: Some(tx),
                }),
            },
            rx,
        )
    }

    pub async fn process(&mut self, actor: &mut A, context: &mut Context<A>) {
        self.inner.handle(actor, context).await
    }
}
