use async_nats::jetstream::Context;
use bytes::Bytes;
use chrono::Utc;

use crate::{
    error::ControllerError,
    types::{AckPayload, AckResult},
};
use ops_keeper::types::PendingOp;

/// Emit to ops.ack.<workspace>.<op_type> (for the original requester).
pub async fn emit_ack(
    js: &Context,
    workspace: &str,
    op: &PendingOp,
    jti: &str,
    result: AckResult,
    error: Option<String>,
) -> Result<(), ControllerError> {
    let payload = AckPayload {
        jti: jti.to_string(),
        workspace: workspace.to_string(),
        op_type: op.op_type.clone(),
        target: op.target.clone(),
        result,
        error,
        timestamp: Utc::now(),
    };
    let bytes = Bytes::from(serde_json::to_vec(&payload)?);
    let subject = format!("ops.ack.{}.{}", workspace, op.op_type);
    js.publish(subject.clone(), bytes)
        .await
        .map_err(|e| ControllerError::Nats(format!("publish ack to {subject}: {e}")))?
        .await
        .map_err(|e| ControllerError::Nats(format!("ack confirm for {subject}: {e}")))?;
    Ok(())
}

/// Emit to ops.audit.<workspace> (consumed by audit-mirror → Radicle).
pub async fn emit_audit(
    js: &Context,
    workspace: &str,
    op: &PendingOp,
    jti: &str,
    outcome: &str,
) -> Result<(), ControllerError> {
    let payload = serde_json::json!({
        "jti": jti,
        "workspace": workspace,
        "op_type": op.op_type,
        "target": op.target,
        "sub": op.sub,
        "expected_state_version": op.expected_state_version,
        "outcome": outcome,
        "timestamp": Utc::now().to_rfc3339(),
    });
    let bytes = Bytes::from(serde_json::to_vec(&payload)?);
    let subject = format!("ops.audit.{workspace}");
    js.publish(subject.clone(), bytes)
        .await
        .map_err(|e| ControllerError::Nats(format!("publish audit to {subject}: {e}")))?
        .await
        .map_err(|e| ControllerError::Nats(format!("audit confirm for {subject}: {e}")))?;
    Ok(())
}
