//! Temporary event subscriptions with timeout and cancellation support.

use std::sync::{Arc, Mutex, mpsc};
use std::time::Duration;

use libremetaverse_types::compat::{
    CancellationToken, Close, EventHandler, ExternalError, TaskCompletionSource,
};

use crate::Error;

fn timeout_duration(timeout_ms: i32) -> Result<Option<Duration>, Error> {
    match timeout_ms {
        -1 => Ok(None),
        value if value >= 0 => Ok(Some(Duration::from_millis(value as u64))),
        _ => Err(Error::Argument),
    }
}

/// Event wait helpers matching the upstream subscribe/unsubscribe lifecycle.
pub struct EventSubscriptionHelper;

impl EventSubscriptionHelper {
    pub fn wait_for_condition<TEventArgs: 'static>(
        subscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        unsubscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        condition: Box<dyn Fn(&Option<TEventArgs>) -> bool + Send + Sync>,
        timeout_ms: i32,
    ) -> Result<(), Error> {
        let timeout = timeout_duration(timeout_ms)?;
        let (sender, receiver) = mpsc::sync_channel(1);
        let handler: EventHandler<Option<TEventArgs>> = Arc::new(move |event| {
            if condition(&event) {
                let _ = sender.try_send(());
            }
        });
        subscribe(Arc::clone(&handler));
        let subscription = EventSubscription::from_registered(unsubscribe, handler);
        match timeout {
            Some(timeout) => {
                let _ = receiver.recv_timeout(timeout);
            }
            None => {
                let _ = receiver.recv();
            }
        }
        subscription.dispose()
    }

    pub async fn wait_for_event_with_action_action_func_func_int32_cancellation_token_t_result<
        TEventArgs: 'static,
        TResult,
    >(
        subscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        unsubscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        filter: Box<dyn Fn(&Option<TEventArgs>) -> bool + Send + Sync>,
        result_selector: Box<dyn Fn(&Option<TEventArgs>) -> Option<TResult> + Send + Sync>,
        timeout_ms: i32,
        cancellation_token: Option<CancellationToken>,
        default_value: Option<TResult>,
    ) -> Result<Option<TResult>, Error>
    where
        TResult: Send + 'static,
    {
        let timeout = timeout_duration(timeout_ms)?;
        let completion = TaskCompletionSource::new();
        let event_completion = completion.clone();
        let handler: EventHandler<Option<TEventArgs>> = Arc::new(move |event| {
            if filter(&event) {
                let _ = event_completion.try_set_result(result_selector(&event));
            }
        });
        subscribe(Arc::clone(&handler));
        let subscription = EventSubscription::from_registered(unsubscribe, handler);

        let cancellation = cancellation_token.map(|token| {
            let completion = completion.clone();
            token.register_callback(Arc::new(move || {
                let _ = completion.try_set_cancelled();
            }))
        });
        if let Some(timeout) = timeout {
            let timeout_completion = completion.clone();
            if std::thread::Builder::new()
                .name("event-wait-timeout".to_owned())
                .spawn(move || {
                    std::thread::sleep(timeout);
                    let _ = timeout_completion.try_set_result(default_value);
                })
                .is_err()
            {
                subscription.dispose()?;
                return Err(Error::InvalidOperation);
            }
        }

        let result = completion.future().await;
        drop(cancellation);
        subscription.dispose()?;
        result
    }

    pub fn wait_for_event_with_action_action_func_func_int32_t_result<
        TEventArgs: 'static,
        TResult,
    >(
        subscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        unsubscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        filter: Box<dyn Fn(&Option<TEventArgs>) -> bool + Send + Sync>,
        result_selector: Box<dyn Fn(&Option<TEventArgs>) -> Option<TResult> + Send + Sync>,
        timeout_ms: i32,
        default_value: Option<TResult>,
    ) -> Result<Option<TResult>, Error>
    where
        TResult: Send + 'static,
    {
        let timeout = timeout_duration(timeout_ms)?;
        let (sender, receiver) = mpsc::sync_channel(1);
        let handler: EventHandler<Option<TEventArgs>> = Arc::new(move |event| {
            if filter(&event) {
                let _ = sender.try_send(result_selector(&event));
            }
        });
        subscribe(Arc::clone(&handler));
        let subscription = EventSubscription::from_registered(unsubscribe, handler);
        let result = match timeout {
            Some(timeout) => receiver.recv_timeout(timeout).unwrap_or(default_value),
            None => receiver.recv().unwrap_or(default_value),
        };
        subscription.dispose()?;
        Ok(result)
    }
}

/// Exactly-once unsubscribe guard.
pub struct EventSubscription<TEventArgs> {
    registered: Mutex<RegisteredEvent<TEventArgs>>,
}

struct RegisteredEvent<TEventArgs> {
    unsubscribe: Option<Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>>,
    handler: Option<EventHandler<Option<TEventArgs>>>,
}

impl<TEventArgs> EventSubscription<TEventArgs> {
    pub fn new(
        subscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        unsubscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        handler: EventHandler<Option<TEventArgs>>,
    ) -> Result<Self, Error> {
        subscribe(Arc::clone(&handler));
        Ok(Self::from_registered(unsubscribe, handler))
    }

    fn from_registered(
        unsubscribe: Box<dyn Fn(EventHandler<Option<TEventArgs>>) + Send + Sync>,
        handler: EventHandler<Option<TEventArgs>>,
    ) -> Self {
        Self {
            registered: Mutex::new(RegisteredEvent {
                unsubscribe: Some(unsubscribe),
                handler: Some(handler),
            }),
        }
    }

    pub fn dispose(&self) -> Result<(), Error> {
        let (unsubscribe, handler) = {
            let mut registered = self
                .registered
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner);
            (registered.unsubscribe.take(), registered.handler.take())
        };
        if let (Some(unsubscribe), Some(handler)) = (unsubscribe, handler) {
            unsubscribe(handler);
        }
        Ok(())
    }
}

impl<TEventArgs> Close for EventSubscription<TEventArgs> {
    fn close(&mut self) -> Result<(), ExternalError> {
        self.dispose()
            .map_err(|error| ExternalError(error.to_string()))
    }
}

impl<TEventArgs> Drop for EventSubscription<TEventArgs> {
    fn drop(&mut self) {
        let _ = self.dispose();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::{AtomicUsize, Ordering};

    #[test]
    fn subscription_unsubscribes_exactly_once() {
        let count = Arc::new(AtomicUsize::new(0));
        let observed = Arc::clone(&count);
        let subscription = EventSubscription::<i32>::new(
            Box::new(|_| {}),
            Box::new(move |_| {
                observed.fetch_add(1, Ordering::Relaxed);
            }),
            Arc::new(|_| {}),
        )
        .unwrap();
        subscription.dispose().unwrap();
        subscription.dispose().unwrap();
        drop(subscription);
        assert_eq!(count.load(Ordering::Relaxed), 1);
    }

    #[test]
    fn synchronous_wait_selects_matching_event_and_unsubscribes() {
        let slot = Arc::new(Mutex::new(None::<EventHandler<Option<i32>>>));
        let subscribe_slot = Arc::clone(&slot);
        let unsubscribe_slot = Arc::clone(&slot);
        let emitter_slot = Arc::clone(&slot);
        let emitter = std::thread::spawn(move || {
            while emitter_slot.lock().unwrap().is_none() {
                std::thread::yield_now();
            }
            let handler = emitter_slot.lock().unwrap().as_ref().unwrap().clone();
            handler(Some(7));
        });
        let result =
            EventSubscriptionHelper::wait_for_event_with_action_action_func_func_int32_t_result(
                Box::new(move |handler| *subscribe_slot.lock().unwrap() = Some(handler)),
                Box::new(move |_| *unsubscribe_slot.lock().unwrap() = None),
                Box::new(|event| event == &Some(7)),
                Box::new(|event| *event),
                1_000,
                None,
            )
            .unwrap();
        emitter.join().unwrap();
        assert_eq!(result, Some(7));
        assert!(slot.lock().unwrap().is_none());
    }
}
