use anyhow::{anyhow, Result};
use bsv_wallet_toolbox::monitor::ArcadeEventsTask;
use bsv_wallet_toolbox::services::providers::arcade::{
arcade_reorg_marker, arcade_verdict, ArcadeReorgMarker, ArcadeStatusEvent, ArcadeVerdict,
};
use bsv_wallet_toolbox::StorageSqlx;
use std::sync::atomic::{AtomicBool, Ordering};
pub const PROOF_NOT_ACCEPTED: &str =
"proof not stored: the toolbox's funnel refused or deferred the path (its log names why); the proof is asked for again";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IngestAction {
ProofIngested,
ProofRejected(String),
StatusApplied,
StatusIgnored,
Reask,
Refused(String),
}
pub async fn ingest_arc_payload(
storage: &StorageSqlx,
payload: &serde_json::Value,
) -> Result<IngestAction> {
let txid = payload
.get("txid")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("payload missing txid"))?;
if txid.len() != 64 || !txid.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(anyhow!("invalid txid"));
}
let ev: ArcadeStatusEvent = serde_json::from_value(payload.clone())
.map_err(|e| anyhow!("not an Arcade status body: {}", e))?;
let reask = AtomicBool::new(false);
let updated = ArcadeEventsTask::<StorageSqlx>::apply_event(storage, &ev, &reask)
.await
.map_err(|e| anyhow!("apply_event: {}", e))?;
let reask = reask.load(Ordering::SeqCst);
let unmined = arcade_reorg_marker(ev.extra_info.as_deref()) == Some(ArcadeReorgMarker::Unmined);
let verdict = arcade_verdict(&ev.tx_status);
let offered_path = ev.merkle_path.as_deref().is_some_and(|p| !p.is_empty());
let action = if unmined {
IngestAction::Reask
} else if verdict == ArcadeVerdict::Invalid {
IngestAction::Refused(format!(
"txStatus {:?} is not a word Arcade defines: nothing applied",
ev.tx_status
))
} else if verdict == ArcadeVerdict::Mined && offered_path {
if reask {
IngestAction::ProofRejected(PROOF_NOT_ACCEPTED.into())
} else {
IngestAction::ProofIngested
}
} else if reask {
IngestAction::Reask
} else if updated {
IngestAction::StatusApplied
} else {
IngestAction::StatusIgnored
};
match &action {
IngestAction::Refused(reason) => {
tracing::warn!(txid = %txid, reason = %reason, "arc-callback: refused")
}
IngestAction::ProofRejected(reason) => tracing::warn!(
txid = %txid,
status = %ev.tx_status,
reason = %reason,
"arc-callback: proof not stored"
),
_ => tracing::info!(
txid = %txid,
status = %ev.tx_status,
extra_info = ?ev.extra_info,
code = ?ev.status_code,
action = ?action,
"arc-callback: Arcade event applied by the toolbox"
),
}
Ok(action)
}