use bytes::Bytes;
use tracing::{debug, error, info, warn};

use crate::{error::KeeperError, types::PendingOp};

/// Parse a raw NATS message payload into a PendingOp.
pub fn parse_pending(payload: &Bytes) -> Result<PendingOp, KeeperError> {
    serde_json::from_slice(payload).map_err(|e| {
        KeeperError::Json(serde_json::Error::io(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            format!("invalid PendingOp JSON: {e}"),
        )))
    })
}

/// Derive the expected ops.cmd.* subject for a signed op.
/// Maps the ops.pending.<ws>.<op_type> subject to ops.cmd.<ws>.<op_type>.
pub fn pending_subject_to_cmd(pending_subject: &str) -> Option<String> {
    let parts: Vec<&str> = pending_subject.splitn(4, '.').collect();
    // Expected: ops.pending.<workspace>.<op_type>
    if parts.len() == 4 && parts[0] == "ops" && parts[1] == "pending" {
        Some(format!("ops.cmd.{}.{}", parts[2], parts[3]))
    } else {
        None
    }
}

/// Extract op_type from an ops.pending.* subject.
pub fn extract_op_type(pending_subject: &str) -> Option<&str> {
    pending_subject.rsplit('.').next()
}

/// Log a decision result at the appropriate level.
pub fn log_decision(op: &PendingOp, decision: &str) {
    match decision {
        "auto_sign" => info!(
            op_type = %op.op_type,
            target  = %op.target,
            sub     = %op.sub,
            "keeper auto-signing op"
        ),
        "require_manual" => warn!(
            op_type = %op.op_type,
            target  = %op.target,
            sub     = %op.sub,
            "op requires manual signature via keeper-cli"
        ),
        "hold_pending" => debug!(
            op_type = %op.op_type,
            target  = %op.target,
            "no policy rule matched; op remains in pending queue"
        ),
        other => error!(decision = other, "unknown decision value"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_valid_pending_op() {
        let json = r#"{"op_type":"scale","target":"staging-vapora","sub":"woodpecker-42","expected_state_version":"v7"}"#;
        let op = parse_pending(&Bytes::from(json)).unwrap();
        assert_eq!(op.op_type, "scale");
        assert_eq!(op.target, "staging-vapora");
    }

    #[test]
    fn subject_mapping_roundtrip() {
        let pending = "ops.pending.libre-wuji.scale";
        let cmd = pending_subject_to_cmd(pending).unwrap();
        assert_eq!(cmd, "ops.cmd.libre-wuji.scale");
    }

    #[test]
    fn subject_mapping_rejects_malformed() {
        assert!(pending_subject_to_cmd("ops.cmd.libre-wuji.scale").is_none());
        assert!(pending_subject_to_cmd("malformed").is_none());
    }

    #[test]
    fn extract_op_type_from_subject() {
        assert_eq!(extract_op_type("ops.pending.libre-wuji.deploy"), Some("deploy"));
        assert_eq!(extract_op_type("ops.pending.libre-wuji.scale"), Some("scale"));
    }
}
