use crate::app::MyApp;
use crate::app_message::AppMessage;
use crate::state_mutator::StateMutator;
use cloud_terrastodon_relative_location::RelativeLocation;
use eyre::eyre;
use std::panic::Location;
use tokio::task::JoinHandle;
use tracing::error;

pub struct Work<OnEnqueue, OnWork, WorkSuccess, WorkFailureMutator, OnFailure>
where
    OnEnqueue: Fn(&mut MyApp),
    OnWork: Future<Output = eyre::Result<WorkSuccess>> + Send + 'static,
    OnWork::Output: Send + 'static,
    WorkSuccess: StateMutator + 'static,
    OnFailure: Fn(eyre::Error) -> WorkFailureMutator + Send + 'static,
    WorkFailureMutator: StateMutator + 'static,
{
    pub location: &'static Location<'static>,
    pub on_enqueue: OnEnqueue,
    pub on_work: OnWork,
    pub on_failure: OnFailure,
    pub is_err_if_discarded: bool,
    pub description: String,
}

#[derive(Debug)]
pub struct WorkHandle {
    pub join_handle: JoinHandle<eyre::Result<()>>,
    pub description: String,
    pub is_err_if_discarded: bool,
}

impl<OnEnqueue, OnWork, WorkSuccess, WorkFailureMutator, OnFailure>
    Work<OnEnqueue, OnWork, WorkSuccess, WorkFailureMutator, OnFailure>
where
    OnEnqueue: Fn(&mut MyApp),
    OnWork: Future<Output = eyre::Result<WorkSuccess>> + Send + 'static,
    OnWork::Output: Send + 'static,
    WorkSuccess: StateMutator + 'static,
    OnFailure: Fn(eyre::Error) -> WorkFailureMutator + Send + 'static,
    WorkFailureMutator: StateMutator + 'static,
{
    pub fn enqueue(self, app: &mut MyApp)
    where
        OnEnqueue: Fn(&mut MyApp),
        OnWork: Future<Output = eyre::Result<WorkSuccess>> + Send + 'static,
        OnWork::Output: Send + 'static,
        WorkSuccess: StateMutator + 'static,
        OnFailure: Fn(eyre::Error) -> WorkFailureMutator + Send + 'static,
        WorkFailureMutator: StateMutator + 'static,
    {
        let work = self;
        let runtime = tokio::runtime::Handle::current();
        let tx = app.tx.clone();
        let description = work.description;
        let join_handle = runtime.spawn(async move {
            match work.on_work.await {
                Ok(result) => {
                    let msg = AppMessage::StateChange(Box::new(result));
                    if let Err(e) = tx.send(msg)
                        && work.is_err_if_discarded
                    {
                        return Err(eyre!("Error sending message for work success: {}", e));
                    }
                }
                Err(error) => {
                    let error = error
                        .wrap_err(format!(
                            "Work location: {}",
                            RelativeLocation::from(work.location)
                        ))
                        .wrap_err("Error encountered in worker thread");
                    error!("{:?}", error);

                    let state_mutator: WorkFailureMutator = (work.on_failure)(error);
                    let msg = AppMessage::StateChange(Box::new(state_mutator));
                    if let Err(e) = tx.send(msg) {
                        return Err(eyre!("Error sending message for work failure: {:#?}", e));
                    }
                }
            }
            Ok(())
        });
        (work.on_enqueue)(app);
        app.work_tracker.track(WorkHandle {
            description,
            join_handle,
            is_err_if_discarded: work.is_err_if_discarded,
        });
    }
}
