// 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)
//
// `mailjet` — transactional email + SMS via Mailjet REST.
// Param names: resource (email/sms), operation, fromEmail, toEmail,
// subject, text, html, from, to (SMS).
// Credential `mailjetEmailApi` / `mailjetSmsApi`: { apiKey, secretKey,
// sandboxMode (email) / token (sms) }.

use async_trait::async_trait;
use crate::core::error::NodeError;
use crate::core::types::*;
use crate::engine::{ExecuteCtx, Node, NodeFactory};
use serde_json::{json, Value};
use std::sync::Arc;

pub struct MailjetNode;

static META: NodeMetadata = NodeMetadata {
    name: "mailjet",
    display_name: "Mailjet",
    version: &[1, 2],
    group: &["output"],
    description: "Send Mailjet email or SMS",
    inputs: 1,
    outputs: 1,
    properties: &[
        PropertyDef { name: "resource", display_name: "Resource", kind: PropertyKind::Options, required: true, default: Some("email") },
        PropertyDef { name: "operation", display_name: "Operation", kind: PropertyKind::Options, required: true, default: Some("send") },
        PropertyDef { name: "fromEmail", display_name: "From Email", kind: PropertyKind::String, required: false, default: None },
        PropertyDef { name: "toEmail", display_name: "To Email", kind: PropertyKind::String, required: false, default: None },
        PropertyDef { name: "subject", display_name: "Subject", kind: PropertyKind::String, required: false, default: None },
        PropertyDef { name: "text", display_name: "Text", kind: PropertyKind::String, required: false, default: None },
        PropertyDef { name: "html", display_name: "HTML", kind: PropertyKind::String, required: false, default: None },
        PropertyDef { name: "from", display_name: "From (SMS)", kind: PropertyKind::String, required: false, default: None },
        PropertyDef { name: "to", display_name: "To (SMS)", kind: PropertyKind::String, required: false, default: None },
    ],
    credentials: &[CredentialRef { name: "mailjetEmailApi", required: true }],
};

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

    async fn execute(&self, ctx: &mut ExecuteCtx<'_>) -> Result<NodeOutput, NodeError> {
        let resource = ctx.param_opt_str("resource").unwrap_or_else(|| "email".into());

        let base_url = ctx.credential_field("base_url").ok()
            .and_then(|v| v.as_str().map(|s| s.trim_end_matches('/').to_string()))
            .unwrap_or_else(|| "https://api.mailjet.com".to_string());

        match resource.as_str() {
            "email" => {
                let api_key = ctx.credential_field("apiKey")?
                    .as_str().ok_or_else(|| NodeError::MissingCredential("mailjetEmailApi.apiKey".into()))?
                    .to_string();
                let secret_key = ctx.credential_field("secretKey")?
                    .as_str().ok_or_else(|| NodeError::MissingCredential("mailjetEmailApi.secretKey".into()))?
                    .to_string();
                let sandbox = ctx.credential_field("sandboxMode").ok()
                    .and_then(|v| v.as_bool()).unwrap_or(false);
                let from_email = ctx.param_str("fromEmail")?;
                let to_email = ctx.param_str("toEmail")?;
                let subject = ctx.param_str("subject")?;
                let text = ctx.param_opt_str("text").unwrap_or_default();
                let html = ctx.param_opt_str("html");

                let mut message = json!({
                    "From": { "Email": from_email },
                    "To": [{ "Email": to_email }],
                    "Subject": subject,
                    "TextPart": text,
                });
                if let Some(h) = html {
                    message.as_object_mut().unwrap().insert("HTMLPart".into(), Value::String(h));
                }
                let payload = json!({ "Messages": [message], "SandboxMode": sandbox });

                let resp = reqwest::Client::new()
                    .post(format!("{}/v3.1/send", base_url))
                    .basic_auth(&api_key, Some(&secret_key))
                    .json(&payload)
                    .send().await.map_err(|e| NodeError::Http(format!("mailjet: {}", e)))?;
                let status = resp.status();
                let parsed: Value = resp.json().await.unwrap_or(Value::Null);
                if !status.is_success() {
                    return Err(NodeError::Http(format!("mailjet email {}: {}", status, parsed)));
                }
                Ok(vec![vec![NodeItem { json: parsed, ..Default::default() }]])
            }
            "sms" => {
                let token = ctx.credential_field("token")?
                    .as_str().ok_or_else(|| NodeError::MissingCredential("mailjetSmsApi.token".into()))?
                    .to_string();
                let from = ctx.param_str("from")?;
                let to = ctx.param_str("to")?;
                let text = ctx.param_str("text")?;
                let payload = json!({ "From": from, "To": to, "Text": text });
                let resp = reqwest::Client::new()
                    .post(format!("{}/v4/sms-send", base_url))
                    .bearer_auth(&token)
                    .json(&payload)
                    .send().await.map_err(|e| NodeError::Http(format!("mailjet: {}", e)))?;
                let status = resp.status();
                let parsed: Value = resp.json().await.unwrap_or(Value::Null);
                if !status.is_success() {
                    return Err(NodeError::Http(format!("mailjet sms {}: {}", status, parsed)));
                }
                Ok(vec![vec![NodeItem { json: parsed, ..Default::default() }]])
            }
            other => Err(NodeError::Other(format!("mailjet: unknown resource `{}`", other))),
        }
    }
}

inventory::submit! { NodeFactory::new("mailjet", || Arc::new(MailjetNode)) }
