// r8r: rust port of n8n.io
// Initial author: Michael Cochran <mcochran@cochranblock.org>
// Built for: Tanner Woodrum, intended cofounder
// License: Unlicense (public domain dedication, see UNLICENSE)
//
// `schedule` — n8n's modern trigger sibling to `cron`. v0.1 alias of cron
// with the same one-shot semantics. Use `cron_expr` for the schedule.

use async_trait::async_trait;
use crate::core::error::NodeError;
use crate::core::types::*;
use crate::engine::{ExecuteCtx, Node, NodeFactory};
use serde_json::json;
use std::sync::Arc;
use tokio::sync::{oneshot, Mutex};
use tokio_cron_scheduler::{Job, JobScheduler};

pub struct ScheduleNode;

static META: NodeMetadata = NodeMetadata {
    name: "schedule",
    display_name: "Schedule Trigger",
    version: &[1, 2],
    group: &["trigger"],
    description: "Trigger the workflow on a cron-style schedule (alias of cron)",
    inputs: 0,
    outputs: 1,
    properties: &[],
    credentials: &[],
};

#[async_trait]
impl Node for ScheduleNode {
    fn metadata(&self) -> &'static NodeMetadata { &META }

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let cron_expr = ctx.param_str("cron_expr")?;
        let (tx, rx) = oneshot::channel::<chrono::DateTime<chrono::Utc>>();
        let shared = Arc::new(Mutex::new(Some(tx)));

        let mut sched = JobScheduler::new().await
            .map_err(|e| NodeError::Other(format!("schedule init: {}", e)))?;
        let job_shared = shared.clone();
        let job = Job::new_async(cron_expr.as_str(), move |_uuid, _l| {
            let shared = job_shared.clone();
            Box::pin(async move {
                if let Some(tx) = shared.lock().await.take() {
                    let _ = tx.send(chrono::Utc::now());
                }
            })
        }).map_err(|e| NodeError::Other(format!("schedule parse: {}", e)))?;
        sched.add(job).await.map_err(|e| NodeError::Other(format!("schedule add: {}", e)))?;
        sched.start().await.map_err(|e| NodeError::Other(format!("schedule start: {}", e)))?;

        let fired_at = rx.await.map_err(|e| NodeError::Other(format!("schedule recv: {}", e)))?;
        let _ = sched.shutdown().await;
        Ok(vec![vec![NodeItem {
            json: json!({ "fired_at": fired_at.to_rfc3339() }),
            ..Default::default()
        }]])
    }
}

inventory::submit! { NodeFactory::new("schedule", || Arc::new(ScheduleNode)) }
